javaio---Reader类
在前几篇文章中一直讲的都是InputStream,这是操作字节流的类,然而我们在程序中往往要从文件等stream中读取字符信息,如果只用InputStream能否读取字符信息呢?当然可以。但是这涉及到了一个编码和解码的问题,传输双方必须才用同一种编码方式才能正确接收,这就导致每次在传输时,传输方需要做这么几件事:
1)将需要传输的字符编码成指定字节
2)传输字节
接收方需要做这么几件事:
1)接收字节
2)将字节解码成对应的字符
我们看一下下面的例子:
我在对应目录有一个文件,这个文件是按照utf-8编码的,现在利用InputStream读取到一个byte数组中,如果我们想要读取到文件的内容,还需要继续转码成utf-8格式的字符串。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
importjava.io.FileInputStream;
importjava.io.FileNotFoundException;
importjava.io.IOException;
importjava.nio.charset.Charset;
/
Createdbyzhaohuion16-10-14.
/
publicclassCode{
publicstaticvoidmain(String[]args){
try{
FileInputStreaminputStream=newFileInputStream("/home/zhaohui/tmp/zhaohui");
byte[]buf=newbyte[100];
intlength=inputStream.read(buf);
System.out.println("thelengthofbytesis"+length);
//将字节数组中指定位置的字节转码成对应的字符串
Stringcontent=newString(buf,0,length,Charset.forName("utf-8"));
System.out.println("thecontentis"+content);
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
输出:
thelengthofbytesis16
thecontentis?你好吗?
从上面的例子中,我们看到只有InputStream就能解决传输字符串的问题了,但是每次都要先读成byte字节,再进行转码,麻烦,能不能直接传字符呢?????
答案是:不能!!!
计算机只认识0和1,也就是byte,只能传输byte。
但是别人的博客都说Reader和Writer神马的能传啊?这是理解角度的不同,我就认为不能传字符,爱咋咋地!
好的,我现在就正式介绍这个“能”传字符的Reader(Writer类似,我就不说了)。
先用一个例子说明,如果我们直接用Reader读取文件,会是怎样的?
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
importjava.io.;
importjava.nio.charset.Charset;
/
Createdbyzhaohuion16-10-14.
/
publicclassCode{
publicstaticvoidmain(String[]args){
try{
InputStreamin=newFileInputStream("/home/zhaohui/tmp/zhaohui");
InputStreamReaderreader=newInputStreamReader(in,Charset.forName("utf-16"));
char[]buf=newchar[100];
intlength=reader.read(buf);
System.out.println("thelengthis"+length);
for(inti=0;i System.out.println("char["+i+"]is"+buf[i]);
}
System.out.println("thecontentis"+String.valueOf(buf,0,length));
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
输出:thelengthis5
char[0]is你
char[1]is好
char[2]is吗
char[3]is?
char[4]is
thecontentis你好吗?
这样一来,是不是清爽了,也就是在你读取文件的时候,使用Reader可以直接指定解码方式,这样就可以直接读字符内容了。关于编码的问题,比较复杂,有兴趣的请参考网上其他内容,比如Java中的char,是两个字节,但是如果你的文件是utf-8,读取字符时可能就会出现问题,因为utf-8的字符是变长的,有的字符是一个字节,有的是两个,有的是三个。
不是说计算机只能传输字节么,为什么这里能直接读取字符了,好,下面我带大家深入剖析一下Reader类。
废话少说,先上类图:
Java几乎为每一个InputStream都设计了一个对应的Reader,比如如果你想直接读取文件里的字符,可以用FileReader来代替FileInputStream。BufferedReader也是一个装饰者模式的reader,接收一个Reader作为参数,从而对Reader提供缓存功能。
但是这众多的Reader中,却有一部分没什么用(个人观点),先从Reader的源码看起:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicabstractclassReaderimplementsReadable,Closeable{
//每次读取一个字符
publicintread()throwsIOException{
charcb[]=newchar[1];
if(read(cb,0,1)==-1)
return-1;
else
returncb[0];
}
abstractpublicintread(charcbuf[],intoff,intlen)throwsIOException;
//......省略......
}
我这里只列出了Reader的两个灵魂函数,即read()和read(charcbuf[],intoff,intlen)
read()和InputStream中的read()相似,不过这里是只读取一个字符,而这个方法通过调用read(charcbuf[],intoff,intlen)来实现,这个方法是抽象方法,Reader的子类通过实现这个方法达到读取不同介质的目的。
接下来就说说这个Reader家族中最重要的实现类,InputStreamReader类。
先看看这个类的结构:
接着还是先放部分代码上来。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassInputStreamReaderextendsReader{
privatefinalStreamwww.baiyuewang.netDecodersd;
publicInputStreamReader(InputStreamin){
super(in);
try{
sd=StreamDecoder.forInputStreamReader(in,this,(String)null);//##checklockobject
}catch(UnsupportedEncodingExceptione){
//Thedefaultencodingshouldalwaysbeavailable
thrownewError(e);
}
}
publicInputStreamReader(InputStreamin,StringcharsetName)
throwsUnsupportedEncodingException
{
super(in);
if(charsetName==null)
thrownewNullPointerException("charsetName");
sd=StreamDecoder.forInputStreamReader(in,this,charsetName);
}
publicintread()throwsIOException{
returnsd.read();
}
publicintread(charcbuf[],intoffset,intlength)throwsIOException{
returnsd.read(cbuf,offset,length);
}
//.....省略.....
}
从上面的代码可以看出,InputStreamReader有一个重要的域,就是这个
privatefinalStreamDecodersd;
就是这个域帮助InputStreamReader解决了编码的问题。其实这个StreamDecoder类也是Reader的子类,从后面的read()方法也能看出,InputStreamReader的read()其实就是这个sd的read()方法,在剖析StreamDecoder之前,我们再看一眼InputStreamReader的构造方法。
InputStreamReader有四个构造函数,我这里只说前两个,第一个接收一个InputStream作为参数。第二个多了一个charsetName,这就是指定了编码方式,第一种为什么不指定?如果不指定就采用系统默认的编码方式。这在后面的StreamDecode的源码中马上就能看出来。
现在我们再看看StreamDecode的源码:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassStreamDecoderextendsReader{
//StreamDecoder的静态构造方法,如果不指定编码,就采用默认的编码
publicstaticStreamDecoderforInputStreamReader(InputStreamvar0,Objectvar1,Stringvar2)throwsUnsupportedEncodingException{
Stringvar3=var2;
if(var2==null){
var3=Charset.defaultCharset().name();
}
try{
if(Charset.isSupported(var3)){
returnnewStreamDecoder(var0,var1,Charset.forName(var3));
}
}catch(IllegalCharsetNameExceptionvar5){
;
}
thrownewUnsupportedEncodingException(var3);
}
publicstaticStreamDecoderforInputStreamReader(InputStreamvar0,Objectvar1,Charsetvar2){
returnnewStreamDecoder(var0,var1,var2);
}
publicintread()throwsIOException{
returnthis.read0();
}
//由于在java中每个字符都是两个字节,因此这里每次读取两个字节,转成char类型
privateintread0()throwsIOException{
Objectvar1=this.lock;
synchronized(this.lock){
if(this.haveLeftoverChar){
this.haveLeftoverChar=false;
returnthis.leftoverChar;
}else{
char[]var2=newchar[2];
intvar3=this.read(var2,0,2);
switch(var3){
case-1:
return-1;
case0:
default:
assertfalse:var3;
return-1;
case2:
this.leftoverChar=var2[1];
this.haveLeftoverChar=true;
case1:
returnvar2[0];
}
}
}
}
}
这个类的核心就是read()这个方法,由于这里直接操作InputStream进行read(),因此可以读取出2个字节,java中每两个字节转成一个字符。
这就是Reader可以读取字符的原因,只不过是利用InputStream先将字节读取出来,再按照一定的编码方式转码,因此这就是我前面所说的Reader也不能读取字符的原因,因为它只是读字节,转字符而言。
最后再说一说这个BufferedReader,和BufferedInputStream类似,它也是一个装饰者模式的类,接收一个Reader类,提供缓存功能。看看它的源码:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassBufferedReaderextendsReader{
privateReaderin;
//如果不指定缓存长度,就使用默认值
publicBufferedReader(Readerin){
this(in,defaultCharBufferSize);
}
publicintread()throwsIOException{
synchronized(lock){
ensureOpen();
for(;;){
if(nextChar>=nChars){
fill();
if(nextChar>=nChars)
return-1;
}
if(skipLF){
skipLF=false;
if(cb[nextChar]==''\n''){
nextChar++;
continue;
}
}
returncb[nextChar++];
}
}
}
}
我们这里只研究它最简单的构造方法,它的构造函数接收一个Reader对象,并建立一个缓存,如果未指定缓存长度,就使用默认的长度。
BufferedReader的灵魂方法read()和BufferedInputStream的read()方法类似,都是采用了一个fill方法,可以参考javaio--FilterInputStream与装饰者模式这篇文章。
如果没有数据就用fill去读取一块数据,放在缓存里,如果缓存里有数据,直接从缓存里读就OK了。
总结:这篇文章总结了Reader类的用法与原理,但是本文没有具体涉及java的编码问题,这是一个较大的话题,有兴趣的可以去网上参考其他文章。
|
|