1、字符流讀取
字符流讀取的所有類都是從Reader這個超類繼承的,都是用於讀取字符的,這些類分別是InputSteamReader(從字符流讀取)、FileReader(繼承與InputStreamReader,讀取文件流)StringReader(讀取字符串)、PipedReader(讀取管道,管道的上端來自於一個PipedWriter)、CharArrayReader(讀取字符數組),還有兩個比較特殊的類,一個是FileterReader,這是個抽象類,目前只有PushbackReader類實現了它,從名字就可以看出PushbackReader類允許把讀出來的數據推回流中(但能不能推到源中,還有待於驗證);一個是BufferedReader,這個是一個為了提高字符流讀取的性能而提供的帶有緩存功能的類,是一個代理類,能代理前面提到的所有類,也就是說,它可以讀取所有來源的數據。
InputStreamReader和FileReader
這個類的字符流是從字節流讀取字節數據,並且把字節數據轉化為字符的,也就是說字符流的讀操作其實是基於字節流讀操作之上的,內部調取了字節流的讀操作。所以很重要的兩個傳入參數就是字節流和字符編碼集。因為文件操作很重用,所有從InputStreamReader里面有專門派生出了一個FileReader,FileReader因為讀取的是文件,字符流可以用文件或者文件名代替,這樣源就是且只能是一個文件了。
這個類與其他讀字符流讀取類最大的區別,就是它沒有mark、skip、reset等和讀取位置有關的方法,這是因為他是從流中讀取字節數據的,因此沒有辦法對流進行標記,只能老老實實重頭到尾按順序取字節流。所以他只有close,read,ready,getEncoding四個函數
PushbackReader
這個類主要是提供了unread()操作,可以把字符寫回。
PipedReader
這個類從和它相連(snc.connect(src)的PipedWriter中讀取字節流。
BufferedReader
這個類主要是提供一個其它類的代理功能,並且提供一個緩存,尤其是用在InputStream時,更能提高效率,因為他可以提前把字節流讀取到緩存中,所以其他類一般都不會直接用,而是用BufferedReader包裝一下再用。特別需要注意的是,這個類沒有提供直接讀取字節流的構造函數,也就是說它必須從字符流中讀取數據。與它對應的Buffer額度Writer也是這樣,只能把字符寫到字符流中,而不能直接寫到字節流中去。也就是說,它沒有提供字符到字節的轉碼功能。
注意:為了優化性能,所有的數據字符流讀取類從字節流中讀取數據時不是一個一個字符的數據量讀的,而是一下讀取很多數據,然后把這些數據放在緩存里面,再根據需求從緩存里面把字符讀出來。所以,當有兩個字符流讀取對象對同一個block字節流進行讀取時,雖然前一個字符流只讀取了一個字符出來,但是因為字節的預讀取,所以后一個對象可能就無法從字節流中讀取數據了,如果此時后面這個對象的緩存里面也沒有數據,它就會一直卡在那里,等待字節流的數據。
1 public class InputStreamIO { 2 public static void main(String args[]) { 3 try { 4 int count = 0; 5 InputStreamReader inputStreamReader = new InputStreamReader(System.in); 6 PushbackReader pushbackReader = new PushbackReader(new InputStreamReader(System.in)); 7 System.out.println("the ecoding of inputStreamReader is :" + inputStreamReader.getEncoding()); 8 char mychar; 9 do { 10 mychar = (char)inputStreamReader.read(); 11 // pushbackReader.unread(mychar); 12 System.out.println("char from inputStreadReader:"+mychar); 13 System.out.println("char from pushbackReader:"+(char)pushbackReader.read()); 14 } while (mychar!='q'); 15 } 16 catch (IOException e){ 17 System.out.println(e.getMessage()); 18 e.printStackTrace(); 19 } 20 } 21 }
上面的代碼是從標准輸入流中讀取數據的,結果如下:
the ecoding of inputStreamReader is :GBK 123456 char from inputStreadReader:1 12 char from pushbackReader:1 char from inputStreadReader:2 char from pushbackReader:2 char from inputStreadReader:3 char from pushbackReader: char from inputStreadReader:4
上面的結果中,當輸入123456之后,inputStreadReader讀取了一個字符到mychar中,雖然只讀取了一個字符,但是它把標准輸入流中的所有數據都讀完了,存在它的緩存里面,后面的pushbackReader因為自己的緩存里面沒有數據,所以它試圖從標准輸入中獲取字節,但此時標准輸入中所有的字節都被前面的inputStreadReader讀取完了,標准輸入流里已經沒有數據了,所以后面的pushbackReader就只能一直等在那里。
第二次輸入12之后,標准輸入流里面就有了“12\n“(注意,因為標准輸入流以換行符提交刷新,即flush,所以最后肯定會有一個換行符),這是pushbackReader便有了字節可以讀取,它一下子把“12\n”讀取完了,存在緩存里,並且從緩存里讀取出1,被打印了出來
之后循環繼續,到InputStreamReader時,它的緩存里面還有"23456\n",所以它不需硬要從標准輸入流中讀如字節數據,所以它不會卡在那里,他順利的讀取了2;同樣,后面的pushbackReader它也還有“2\n”,它把2讀了出來了。這樣順利的循環下去,知道pushbackReader讀取我它緩存里的最后一個數據“\n”之后,它又卡在了那里。
2、字符流寫
字符流寫就是把字符寫到相應的目的地,並且和字符流讀是對應的,基本上就是把原來的xxxReader變成xxxWriter,然后功能就由讀變成了寫,源也就變成了對應的目的地。如果目的地是字符流(這種情況只有OutputStreamWriter以及它的子類FileWriter),則會把字符自動編碼成字節后再寫入流當中。
PrintWriter這個類比較特殊,下面重點說說它。
PrintWriter
PrintWriter其實是OutputStreamWriter和FileWriter的增強版,它包括了格式化輸出,按行輸出以及自動添加換行符等功能(println)。另外有一點需要注意,它添加的換行符是和底層操作系統有關的,在Windows下,它添加的是回車換行符”\r\n“,在GNU下,他添加的是換行符“\n“。PrintWriter還能自動刷新緩存,每當調用了println, printf, 或者format中的任何一個函數之后,就自動flush。
我們還將馬上看到,在字節流輸出中,也有一個和PrintWriter比較像的叫PrintStream的對象,它也實現了把字符輸出到字節流並提供編碼功能的方法(所以說它比較特殊),但是它的換行符總是“\n”,且遇到“\n”就調用flush。
3、重點分析分析PipedReader和PipedWriter
PipedReader和PipedWriter是連起來用的,用於線程間的IO通信。PipedWriter是PipedReader的src(源),PipedReader是PipedWriter的snk(目標),他們通過二者任何一個connect函數連接,也可以在通過構造函數參數建立連接,並且src和snk是一對一的關系,否則會拋出IOException("Already connected")
異常。
其實src和snk通信挺簡單的,就是src的writer
向snk的buffer里面寫數據,具體的方式這是調用snk的receive
函數把字符傳給snk的buffer。下面重點看看PipedReader的receive
函數的源碼:
1 /** 2 * Receives a char of data. This method will block if no input is 3 * available. 4 */ 5 synchronized void receive(int c) throws IOException { 6 if (!connected) { 7 throw new IOException("Pipe not connected"); 8 } else if (closedByWriter || closedByReader) { 9 throw new IOException("Pipe closed"); 10 } else if (readSide != null && !readSide.isAlive()) { 11 throw new IOException("Read end dead"); 12 } 13 14 writeSide = Thread.currentThread(); 15 while (in == out) { 16 if ((readSide != null) && !readSide.isAlive()) { 17 throw new IOException("Pipe broken"); 18 } 19 /* full: kick any waiting readers */ 20 notifyAll(); 21 try { 22 wait(1000); 23 } catch (InterruptedException ex) { 24 throw new java.io.InterruptedIOException(); 25 } 26 }
/* 說明此PipedReader空了,此時把in和out都歸位到0*/ 27 if (in < 0) { 28 in = 0; 29 out = 0; 30 } 31 buffer[in++] = (char) c; 32 if (in >= buffer.length) { 33 in = 0; 34 } 35 }
還有PipedReader的的read
函數的源碼:
1 public synchronized int read() throws IOException { 2 if (!connected) { 3 throw new IOException("Pipe not connected"); 4 } else if (closedByReader) { 5 throw new IOException("Pipe closed"); 6 } else if (writeSide != null && !writeSide.isAlive() 7 && !closedByWriter && (in < 0)) { 8 throw new IOException("Write end dead"); 9 } 10 11 readSide = Thread.currentThread(); 12 int trials = 2; 13 while (in < 0) { 14 if (closedByWriter) { 15 /* closed by writer, return EOF */ 16 return -1; 17 } 18 if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) { 19 throw new IOException("Pipe broken"); 20 } 21 /* might be a writer waiting */ 22 notifyAll(); 23 try { 24 wait(1000); 25 } catch (InterruptedException ex) { 26 throw new java.io.InterruptedIOException(); 27 } 28 } 29 int ret = buffer[out++]; 30 if (out >= buffer.length) { 31 out = 0; 32 } 33 if (in == out) { 34 /* now empty */ 35 in = -1; 36 } 37 return ret; 38 }
可以看到,PipedReader是通過in和out兩個指針來確定寫入和讀出的,如果有數據且不滿,那么in!=out;當in==out且不等於-1時,表示buffer滿了;當in=-1時,表示buffer空了。buffer是一個首尾相接的數組,從out到in是緩存的數據。從receive函數的源碼中可以很容易地看出,當buffer滿了(in==out且in>0),receive函數會喚醒其它線程,並等待1000ms讓其它線程(讀線程)有機會把數據讀走。並且注意到,receive函數是同步的,也就是說只能有有個線程在寫數據,並且可以肯定的是,這個線程是個寫數據線程(snk的receive函數一般只由src的write函數調用,因為receive函數的訪問權限是默認訪問權限,只有同一個包小面的類才能訪問它)。
另外,從PipedReader的read函數也可以看出,當buffer空了的時候(in<0),他也會喚醒其它線程,並且等待1000ms以期待其它線程寫入數據到buffer。