配色: 字号:
java io --- Reader类
2016-10-17 | 阅:  转:  |  分享 
  
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的编码问题,这是一个较大的话题,有兴趣的可以去网上参考其他文章。

献花(0)
+1
(本文系thedust79首藏)