本文參考鏈接:java
http://blog.csdn.net/class281/article/details/24849275api
http://zhhphappy.iteye.com/blog/1562427數組
http://wdhdmx.iteye.com/blog/1279400緩存
1、IO包簡要類圖安全
Java I/O流部分分爲兩個模塊,即Java1.0中就有的面向字節的流(Stream),以及Java1.1中大幅改動添加的面向字符的流(Reader & Writer)。添加面向字符的流主要是爲了支持國際化,舊的I/O流僅支持8位的字節流,並不能很好的處理16位的Unicode字符(Java的基礎類型char也是16位的Unicode)。下面就針對這兩類流作一個簡要的分析。多線程
2、面向字節的流app
InputStream(OutputStream)是全部面向字節流的基類。它的子類分爲兩大塊,一是諸如ByteArrayInputStream(ByteArrayOutputStream),FileInputStream(FileOutputStream)等等面向各類不一樣的輸入(輸出)源的子類,另外一塊爲了方便流操做的而進一步封裝的裝飾器系列FilterInputStream(FilterOutputStream)類及其子類。async
BufferedInputStream 是一個帶有內存緩衝的 InputStream.函數
1.首先來看類結構 :oop
BufferedInputStream是繼承自FilterInputStream。
FilterInputStream繼承自InputStream屬於輸入流中的連接流,同時引用了InputStream,將InputStream封裝成一個內部變量,同時構造方法上須要傳入一個InputStream。這是一個典型的裝飾器模式,他的任何子類均可以對一個繼承自InputStream的原始流或其餘連接流進行裝飾,如咱們經常使用的使用BufferedInputStream對FileInputStream進行裝飾,使普通的文件輸入流具有了內存緩存的功能,經過內存緩衝減小磁盤io次數。
1 protected volatile InputStream in; 2 protected FilterInputStream(InputStream in) { 3 this.in = in; 4 }
注意:成員變量in使用了volatile關鍵字修飾,保障了該成員變量多線程狀況下的可見性。
2.內存緩衝的實現
概要的瞭解完BufferedInputStream的繼承關係,接下來詳細理解BufferedInputStream是如何實現內存緩衝。既是內存緩衝,就涉及到內存的分配,管理以及如何實現緩衝。
經過構造方法能夠看到:初始化了一個byte數組做爲內存緩衝區,大小能夠由構造方法中的參數指定,也能夠是默認的大小。
1 protected volatile byte buf[]; 2 private static int defaultBufferSize = 8192; 3 public BufferedInputStream(InputStream in, int size) { 4 super(in); 5 if (size <= 0) { 6 throw new IllegalArgumentException("Buffer size <= 0"); 7 } 8 buf = new byte[size]; 9 } 10 public BufferedInputStream(InputStream in) { 11 this(in, defaultBufferSize); 12 }
看完構造函數,大概能夠了解其實現原理:經過初始化分配一個byte數組,一次性從輸入字節流中讀取多個字節的數據放入byte數組,程序讀取部分字節的時候直接從byte數組中獲取,直到內存中的數據用完再從新從流中讀取新的字節。那麼從api文檔中咱們能夠了解到BufferedStream大概具有以下的功能:
從api能夠了解到BufferedInputStream除了使用一個byte數組作緩衝外還具有打標記,重置當前位置到標記的位置從新讀取數據,忽略掉n個數據。這些功能都涉及到緩衝內存的管理,首先看下相關的幾個成員變量:
1 protected int count; 2 protected int pos; 3 protected int markpos = -1; 4 protected int marklimit;
count表示當前緩衝區內總共有多少有效數據;pos表示當前讀取到的位置(即byte數組的當前下標,下次讀取從該位置讀取);markpos:打上標記的位置;marklimit:最多能mark的字節長度,也就是從mark位置到當前pos的最大長度。
從最簡單的read()讀取一個字節的方法開始看:
1 public synchronized int read() throws IOException { 2 if (pos >= count) { 3 fill(); 4 if (pos >= count) 5 return -1; 6 } 7 return getBufIfOpen()[pos++] & 0xff; 8 }
1 /** 2 * Fills the buffer with more data, taking into account 3 * shuffling and other tricks for dealing with marks. 4 * Assumes that it is being called by a synchronized method. 5 * This method also assumes that all data has already been read in, 6 * hence pos > count. 7 */ 8 private void fill() throws IOException { 9 byte[] buffer = getBufIfOpen(); 10 if (markpos < 0) 11 pos = 0; /* no mark: throw away the buffer */ 12 else if (pos >= buffer.length) /* no room left in buffer */ 13 if (markpos > 0) { /* can throw away early part of the buffer */ 14 int sz = pos - markpos; 15 System.arraycopy(buffer, markpos, buffer, 0, sz); 16 pos = sz; 17 markpos = 0; 18 } else if (buffer.length >= marklimit) { 19 markpos = -1; /* buffer got too big, invalidate mark */ 20 pos = 0; /* drop buffer contents */ 21 } else if (buffer.length >= MAX_BUFFER_SIZE) { 22 throw new OutOfMemoryError("Required array size too large"); 23 } else { /* grow buffer */ 24 int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? 25 pos * 2 : MAX_BUFFER_SIZE; 26 if (nsz > marklimit) 27 nsz = marklimit; 28 byte nbuf[] = new byte[nsz]; 29 System.arraycopy(buffer, 0, nbuf, 0, pos); 30 if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { 31 // Can't replace buf if there was an async close. 32 // Note: This would need to be changed if fill() 33 // is ever made accessible to multiple threads. 34 // But for now, the only way CAS can fail is via close. 35 // assert buf == null; 36 throw new IOException("Stream closed"); 37 } 38 buffer = nbuf; 39 } 40 count = pos; 41 int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 42 if (n > 0) 43 count = n + pos; 44 }
/** * See the general contract of the <code>mark</code> * method of <code>InputStream</code>. * * @param readlimit the maximum limit of bytes that can be read before * the mark position becomes invalid. * @see java.io.BufferedInputStream#reset() */ public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; } /** * See the general contract of the <code>reset</code> * method of <code>InputStream</code>. * <p> * If <code>markpos</code> is <code>-1</code> * (no mark has been set or the mark has been * invalidated), an <code>IOException</code> * is thrown. Otherwise, <code>pos</code> is * set equal to <code>markpos</code>. * * @exception IOException if this stream has not been marked or, * if the mark has been invalidated, or the stream * has been closed by invoking its {@link #close()} * method, or an I/O error occurs. * @see java.io.BufferedInputStream#mark(int) */ public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; }
當pos>=count的時候也就是表示當前的byte中的數據爲空或已經被讀完,他調用了一個fill()方法,從字面理解就是填充的意思,其實是從真正的輸入流中讀取一些新數據放入緩衝內存中,以後直到緩衝內存中的數據讀完前都不會再從真正的流中讀取數據。
看源碼中的fill()方法有很大一段是關於markpos的處理,其處理過程大體以下圖:
a.沒有markpos的狀況很簡單:
b.有mark的狀況比較複雜:
3.read()方法返回值
以上即爲內存緩衝管理的徹底過程,再回過頭看read()方法,當緩衝byte數組中有數據能夠讀時,直接從數組中讀取一個字節,但最後的read方法返回的倒是int,並且還和0xff作了與運算。
1 return getBufIfOpen()[pos++] & 0xff;
爲何不直接返回一個byte,而是一個與運算後的int。首先宏觀的看InputStream和Reader兩個輸入流的抽象類都定義了read接口並且都返回int,一個是字節流,一個是字符流。咱們知道字節用byte表示,字符用char表示。首先看java中基本類型的取值範圍:
從取值範圍來看int包含了char和byte,這爲使用int做爲返回值類型提供了可能。
在應用中咱們通常用read()接口的返回值是-1則表示已經讀到文件尾(EOF)。
char的取值範圍自己不包含負數,全部用int的-1表示文件讀完沒問題,但byte的取值範圍-128 ~ 127,包含了-1,讀取的有效數據範圍就是-128~127,沒辦法用這個取值範圍中的任何一個數字表示異常或者數據已經讀完,因此接口若是直接使用byte做爲返回值不可行,直接將byte強制類型轉換成int也不行,由於若是讀到一個byte的-1,轉爲int了也是-1,會被理解爲文件已經讀完。因此這裏作了一個特殊處理return getBufIfOpen()[pos++] & 0xff。
0xff是int類型,二進制爲0000 0000 0000 0000 0000 0000 1111 1111。
上述的與運算實際上讀取的byte先被強制轉換成了int,例如byte的-1(最高位表示符號位,以補碼的形式表示負數爲:1111 1111)
轉換爲int以後的二進制1111 1111 1111 1111 1111 1111 1111 1111
& 0xff以後高位去0
最後返回的結果是0000 0000 0000 0000 0000 0000 1111 1111, 爲int值爲256
其-128~-1被轉爲int中128~256的正數表示。
這樣解決了能夠用-1表示文件已經讀完。但關鍵是數據的值發生了變化,真正要用讀取的數據時是否還能拿到原始的byte。還拿上面那個例子來看,當讀取返回一個256時,將其強制類型轉換爲byte,(byte)256獲得byte的-1,由於byte只有8位,當int的高位被丟棄後就只剩下1111 1111,在byte中高位的1表示符號位爲負數,最終的結果便是byte的-1;一樣byte的-128(1000 0000)被轉爲int的128(0000 0000 0000 0000 0000 0000 1000 0000),強制類型轉換後還原byte的1000 0000。
4.線程安全
返回值中還有一個細節是getBufIfOpen()[pos++],直接將pos++來獲取下一個未讀取的數據,這裏涉及到的兩個元素:一個內存數組,一個當前讀取的數據下標都是全局變量,pos++也不是線程安全。那麼BufferedInputStream如何保證對內存緩衝數組的操做線程安全?源碼中有操做的public方法除了close方法以外,其餘方法上都加上了synchronized關鍵字,以保障上面描述的整個內存緩存數組的操做是線程安全的。但爲何close方法沒有synchronized,咱們看這個方法作了些什麼事情:
byte[] buffer; while ( (buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close(); return; } // Else retry in case a new buf was CASed in fill() }
簡單來看作了兩個操做:把內存數組置爲null,將引用的inputStream置爲null,同時將引用的inputStream.close();
這兩個操做的核心都是關閉原始流,釋放資源,若是加了synchronized關鍵字,會致使當前線程正在執行read方法,並且系統消耗很大時,想釋放資源沒法釋放。此時read方法還沒執行完,咱們知道synchronized的鎖是加在整個對象上的,因此close方法就必須等到read結束後才能執行,這樣很明顯不能知足close的需求,甚至會致使大量的io資源被阻塞不能關閉。
但該方法用一個while循環,並且只有當bufUpdater.compareAndSet(this, buffer, null)成功時,才執行上述的資源釋放。
先看bufUpdater這個全局變量
protected volatile byte buf[]; private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class, byte[].class, "buf");
AtomicReferenceFieldUpdater是一個抽象類,但該類的內部已經給出了包訪問控制級別的一個實現AtomicReferenceFieldUpdaterImpl,原理是利用反射將一個 被聲明成volatile 的屬性經過JNI調用,使用cpu指令級的命令將一個變量進行更新,保障該操做是原子的。也就是經過上面定義的bufUpdater將buf這個byte數組的跟新變爲原子操做,其做用是保障其原子更新。
BufferedInputStream源代碼中總共有兩個地方用到了這個bufUpdater,一個是咱們上面看到的close方法中,另一個是再前面說道的fill()方法中。既然BufferedInputStream的全部操做上都用了synchronized來作同步,那爲何這裏還須要用這個原子更新器呢?帶着問題上面提到過fill()方法中的最後一個步驟:當有mark,並且markLimit的長度又大於初始數組的長度時,須要對內存數組擴容,即建立一個尺寸更大的數組,將原來數組中的數據拷貝到新數組中,再將指向原數組的應用指向新的數組。bufUpdater正是用在了將原數組引用指向新數組的操做上,一樣close的方法使用的bufUpdater也是用在對數組引用的改變上,這樣看來就比較清晰了,主要是爲了防止一個線程在執行close方法時,將buffer賦值爲null這個時候另一個線程正在執行fill()方法的最後一個步驟又將buffer賦值給了一個新的數組,從而致使資源沒有釋放掉。
下面看一下 bufferedOutputStream的源碼,
相比之下緩衝輸出流就簡單不少,基本上write方法能夠說明一切了:
在緩衝區(也是字節數組)寫滿以前調用write方法只是將數據複製到內部緩衝區;
在緩衝區寫滿以後,現將舊的數據寫入到底層輸出流,而後將新的數據暫存到緩衝區(新的數據並未同時寫入)
因而flush方法也找到了存在的意義:將現有數據所有寫入。
1 package java.io; 2 3 public class BufferedOutputStream extends FilterOutputStream { 4 // 保存「緩衝輸出流」數據的字節數組 5 protected byte buf[]; 6 7 // 緩衝中數據的大小 8 protected int count; 9 10 // 構造函數:新建字節數組大小爲8192的「緩衝輸出流」 11 public BufferedOutputStream(OutputStream out) { 12 this(out, 8192); 13 } 14 15 // 構造函數:新建字節數組大小爲size的「緩衝輸出流」 16 public BufferedOutputStream(OutputStream out, int size) { 17 super(out); 18 if (size <= 0) { 19 throw new IllegalArgumentException("Buffer size <= 0"); 20 } 21 buf = new byte[size]; 22 } 23 24 // 將緩衝數據都寫入到輸出流中 25 private void flushBuffer() throws IOException { 26 if (count > 0) { 27 out.write(buf, 0, count); 28 count = 0; 29 } 30 } 31 32 // 將「數據b(轉換成字節類型)」寫入到輸出流中 33 public synchronized void write(int b) throws IOException { 34 // 若緩衝已滿,則先將緩衝數據寫入到輸出流中。 35 if (count >= buf.length) { 36 flushBuffer(); 37 } 38 // 將「數據b」寫入到緩衝中 39 buf[count++] = (byte)b; 40 } 41 42 public synchronized void write(byte b[], int off, int len) throws IOException { 43 // 若「寫入長度」大於「緩衝區大小」,則先將緩衝中的數據寫入到輸出流,而後直接將數組b寫入到輸出流中 44 if (len >= buf.length) { 45 flushBuffer(); 46 out.write(b, off, len); 47 return; 48 } 49 // 若「剩餘的緩衝空間 不足以 存儲即將寫入的數據」,則先將緩衝中的數據寫入到輸出流中 50 if (len > buf.length - count) { 51 flushBuffer(); 52 } 53 System.arraycopy(b, off, buf, count, len); 54 count += len; 55 } 56 57 // 將「緩衝數據」寫入到輸出流中 58 public synchronized void flush() throws IOException { 59 flushBuffer(); 60 out.flush(); 61 } 62 }
以文件爲輸入輸出目標的類,其實也能夠想象獲得,讀寫本地文件的類追溯上去確定是本地方法。因此固然它的一系列read(write)方法都是native的。這個之後若是以機會的話再研究。
目前能看到的輔助功能有:
讀寫文件的這一溜方法大都在sun.nio.ch和sun.misc包裏,有興趣的能夠去看openjdk提供的源碼,不過Java裏面也是調用native方法,並且考慮到跨平臺特性估計設計上也會更加複雜,因此推薦先去了解C的文件讀寫。
這兩個類在不少地方被翻譯成內存輸入(輸出)流,當時俺就被這高大上的名字深深的折服了。
其實它們的功能、實現都很是簡單,先把全部的數據全存到它內部的字節數組裏,而後用這個數組來繼續讀寫,這個時候底層流你就能夠不用管了,愛關就關沒有影響。
常常會有人把這兩個類混在一塊兒,因而特意在此比劃一番。說到它們的區別,但實際上從類的組織結構上能夠看出來,這兩個類其實沒有什麼聯繫:一個是之內存中的字節數組爲輸入目標的類,一個是爲了更好的操做字節輸入流而提供的帶有緩衝區的裝飾器類。它們的使用目的本就不同,只不過因爲名字似曾相識,打扮得(實現方式)也差很少,因此常常被誤認爲兩兄弟。這兩兄弟的差距仍是蠻大的:
ByteArrayInputStream須要在內部的保存流的全部數據,因此須要一個足夠大的字節數組,但數組的容量是受JVM中堆空間大小限制的,更極端的狀況,即便你爲JVM分配了一個很大的空間,因爲Java數組使用的是int型索引,因此你也猜到了,它仍是會被限制在INT_MAX範圍之內。在底層流數據所有保存到ByteArrayInputStream後,你就能夠不用再管流,轉而去從ByteArrayInputStream讀取數據了。
而BufferedInputStream只會用一個有限大小的緩存數組保存底層流的一小部分數據,你在讀取數據的時候其實仍是在和底層流打交道,只不過BufferedInputStream爲了知足你變幻莫測的讀取要求提供了緩衝區,讓你的讀取操做更加犀利流暢。
因此總結起來,它們除了一樣從InputStream派生而來,一樣使用了字節數組(這是個常常發生的巧合)之外,沒有任何聯繫。
這兩個類容許咱們以基本類型的形式操做字節流,能夠理解爲一個基本數據類型到字節之間的映射轉換。
例如我想要從輸入流中讀取一個int類型數據(4字節),那麼就須要先讀4個字節,而後按照每一個字節在int中的位置做相應移位處理,就獲得這4個字節所表明的int型數據了。
這個類的功能其實比較隱晦(至少我一開始是理解錯了)。按照字面意思理解,它應該是爲了在讀取必定量字節以後,容許咱們調用unread方法,重讀這部分字節。實現上,它的內部也有一個字節數組做緩衝,恩,看起來一切正常。
但是測試它的unread(byte[])方法的時候發現被坑了,調用unread(byte[])以後再讀取,讀到的實際上是你push進去的這個字節數組。它其實沒有想象的那麼聰明,在調用unread(byte[])方法的時候,只是很萌的把你傳給它的這個字節數組當成你以前讀取的數據,把它直接複製到內部緩衝區裏。
也就是說,徹底是 push what, get what…
ObjectInputStream(ObjectOutputStream)與Serializable接口等一塊兒構成了Java的序列化機制,其中牽涉到對象數據的描述、保存與恢復,在此暫不討論。
Reader(Writer)是全部面向字符的流的基類。它一樣有爲了適配各類不一樣輸入源的子類如PipedReader(PipedWriter)、CharArrayReader(CharArrayWriter)等,其中的FileReader(FileWriter)類是直接繼承自InputStreamReader(OutputStreamWriter)。故在此主要關注以Stream爲輸入輸出的InputStreamReader(OutputStreamWriter)類,以及BufferedReader(BufferedWriter)等裝飾器類。
BufferedInputStream | BufferedReader |
---|---|
count | nChars |
pos | nextChar |
markpos | markedChar |
marklimit | readAheadLimit |
下面貼一下BufferedReader和BufferedWriter一些主要方法的源碼
public class BufferedReader extends Reader { //這個又是裝飾模式 private Reader in; }
1 public BufferedReader(Reader in) { 2 this(in, defaultCharBufferSize); 3 } 4 //默認緩存數組的大小 5 private static int defaultCharBufferSize = 8192; 6 //構造方法 7 public BufferedReader(Reader in, int sz) { 8 //這個方法可參考前面的Writer源碼,只要是將鎖賦值 9 super(in); 10 if (sz <= 0) 11 throw new IllegalArgumentException("Buffer size <= 0"); 12 //裝飾模式。。 13 this.in = in; 14 cb = new char[sz]; 15 nextChar = nChars = 0; 16 } 17 //兩個上面用到的參數,用於緩存數據,是字符(char)數組,不是字節(byte)數組。 18 private char cb[]; 19 private int nChars, nextChar;
在看read方法以前先看一眼 標記mark有關的方法有點幫助。爲看懂read作鋪墊
1 //標記流中的當前位置,帶入的參數表示標記所佔的空間 2 public void mark(int readAheadLimit) throws IOException { 3 if (readAheadLimit < 0) { 4 throw new IllegalArgumentException("Read-ahead limit < 0"); 5 } 6 synchronized (lock) { 7 ensureOpen(); 8 this.readAheadLimit = readAheadLimit; 9 markedChar = nextChar; 10 markedSkipLF = skipLF; 11 } 12 } 13 //回到標記位置 14 public void reset() throws IOException { 15 synchronized (lock) { 16 ensureOpen(); 17 if (markedChar < 0) 18 throw new IOException((markedChar == INVALIDATED) 19 ? "Mark invalid" 20 : "Stream not marked"); 21 //下面兩個參數在讀方法中會有詳細解釋 22 nextChar = markedChar; 23 skipLF = markedSkipLF; 24 } 25 }
這個方法中fill()是重點,有點繞,但看懂後就以爲很清晰,能徹底理解bufferedReader的原理。
看完這個方法再回去看3.3的標記部分,就很容易看懂。
1 public int read() throws IOException { 2 //鎖,看來讀得時候也只能一個方法讀。 3 synchronized (lock) { 4 //確保輸入流不是空。 5 ensureOpen(); 6 //這個循環和while同樣。 7 for (;;) { 8 //下面的判斷爲是否下一個讀取的字符超出了緩存數組中實際包含數據的大小。 9 if (nextChar >= nChars) { 10 //下一個字符超出或者等於緩存數組的大小 11 //這個是核心的方法,裏面有標記的內容,詳細的看下面內容。 12 fill(); 13 //若是仍是超出,則表示輸入流讀完了。 14 if (nextChar >= nChars) 15 return -1; 16 } 17 //若是下一個字符是換行符.這個變量只有在readLine裏面才變爲true。和\n\r有關,可忽略。針對不一樣的平臺的 18 if (skipLF) { 19 skipLF = false; 20 if (cb[nextChar] == '\n') { 21 nextChar++; 22 continue; 23 } 24 } 25 //返回當前讀的字符,並將要讀字符+1 26 return cb[nextChar++]; 27 } 28 } 29 } 30 31 //下面的變量是用於fill方法裏的 32 //下面兩個變量是標記的狀態, -1爲未啓動標記,-2爲標記失效。 33 private static final int INVALIDATED = -2; 34 private static final int UNMARKED = -1; 35 //標記的位置 36 private int markedChar = UNMARKED; 37 //nChars表示如今緩存數組中已經存在多少個字符。 38 //nextChar表示下一個讀取的位置,從0開始,這個只是緩存數組中的位置,並非讀取流的位置。 39 private int nChars, nextChar; 40 //標記分配的空間大小。超出後,若是緩存數組從新處置,則標記失效。 41 private int readAheadLimit = 0; 42 43 //將字符數組讀滿,而後直接返回數組中的某個值。裏面主要考慮的是標記的問題。 44 //這個和BufferedInputStream差很少,一個是byte[],這個是char[] 45 private void fill() throws IOException { 46 //計算此次緩存數據的起始位置,起始位置以前保存的是標記的內容。 47 int dst; 48 if (markedChar <= UNMARKED) { 49 //這裏表示沒有使用標記,或者標記失效。 50 dst = 0; 51 } else { 52 //表示使用標記 53 //這個變量表示標記以後實際使用了多少空間 54 int delta = nextChar - markedChar; 55 if (delta >= readAheadLimit) { 56 //若是超過了標記初始的空間。 57 //標記失效 58 markedChar = INVALIDATED; 59 //標記空間賦0 60 readAheadLimit = 0; 61 //緩存數據起點0 62 dst = 0; 63 } else { 64 //若是未超過標記初始的空間。 65 if (readAheadLimit <= cb.length) { 66 //分配的標記空間小於緩存數組的長度 67 //將標記後實際使用長度複製到數組的開始。 68 System.arraycopy(cb, markedChar, cb, 0, delta); 69 //將標記的位置賦0,標記所佔空間仍然是原來的空間,不會縮小。 70 markedChar = 0; 71 //數據緩存的起點 72 dst = delta; 73 } else { 74 //長度不夠,新建一個。 75 char ncb[] = new char[readAheadLimit]; 76 //和上面同樣,複製標記到最前面 77 System.arraycopy(cb, markedChar, ncb, 0, delta); 78 //將引用更新 79 cb = ncb; 80 markedChar = 0; 81 dst = delta; 82 } 83 nextChar = nChars = delta; 84 } 85 } 86 //下面是讀數據,讀出必定長度,默認cb的長度8192,cb在BufferedReader中是惟一的緩存數組。 87 int n; 88 //這個地方讀的方法中不可能返回0.因此只會執行一次 89 do { 90 //從標籤以後讀,讀滿cb字符數組,注意,這裏是調用in的讀方法。 91 n = in.read(cb, dst, cb.length - dst); 92 } while (n == 0); 93 //讀到數據的狀況,沒有讀到的話就不作任何操做。 94 if (n > 0) { 95 //如今緩存空間中已有的真實緩存數量 96 nChars = dst + n; 97 //下一個讀取的位置。 98 nextChar = dst; 99 } 100 }
其它的read方法和這個相似。
一次讀出不少字符的時候,處理的策略是:
a.緩存數組不夠,就用in直接讀,不通過緩存.
b.緩存數組夠,就將緩存中讀出。
c.緩存數組夠,但讀完後還沒讀滿,則繼續從in中接着讀,不夠的部分不過緩存數組。
這個是用的比較多的方法,因此列出來。這個方法在有上面的基礎上,仍是很好懂的。
1 String readLine(boolean ignoreLF) throws IOException { 2 //傳入的布爾值默認爲false 3 StringBuffer s = null; 4 int startChar; 5 6 synchronized (lock) { 7 ensureOpen(); 8 boolean omitLF = ignoreLF || skipLF; 9 //這個是什麼?goto? 10 bufferLoop: 11 //while 12 for (;;) { 13 //下一個字符超出緩存數組大小,這裏nextChar是從0開始的,因此相等的時候就表明已經超出了緩存數組範圍。 14 if (nextChar >= nChars) 15 fill(); 16 //下面的if是判斷流的末尾,讀完了就返回null,或者將以前讀的內容返回 17 if (nextChar >= nChars) { 18 if (s != null && s.length() > 0) 19 return s.toString(); 20 else 21 return null; 22 } 23 //表示沒有到末尾. 24 boolean eol = false; 25 char c = 0; 26 int i; 27 //這個是處理\r\n的狀況,不進行兩次判斷,忽略 28 if (omitLF && (cb[nextChar] == '\n')) 29 nextChar++; 30 skipLF = false; 31 omitLF = false; 32 33 charLoop: 34 //遍歷緩存數組,直到\n或者\r 35 for (i = nextChar; i < nChars; i++) { 36 c = cb[i]; 37 if ((c == '\n') || (c == '\r')) { 38 //表示讀取到了換行 39 eol = true; 40 break charLoop; 41 } 42 } 43 //記錄讀取開始的地方, 44 startChar = nextChar; 45 //要讀的下一個字符。 46 nextChar = i; 47 //讀取到換行,而不是讀完緩存。 48 if (eol) { 49 String str; 50 if (s == null) { 51 str = new String(cb, startChar, i - startChar); 52 } else { 53 s.append(cb, startChar, i - startChar); 54 str = s.toString(); 55 } 56 nextChar++; 57 if (c == '\r') { 58 skipLF = true; 59 } 60 return str; 61 } 62 //表示讀完了緩存數組,還須要繼續讀。 63 if (s == null) 64 s = new StringBuffer(defaultExpectedLineLength); 65 s.append(cb, startChar, i - startChar); 66 } 67 } 68 }
skip 就是先把緩存數組中跳過去,若是緩存數組不夠,就再將數據讀入緩存數組,再跳,一直循環。
ready 表示緩存是否讀完了,沒什麼用處。
markSupported 是否支持標記
close 流等須要關閉的東西都關閉。
1 public class BufferedWriter extends Writer { 2 //裝飾模式 3 private Writer out; 4 }
將緩存數組初始化,而且根據平臺初始化換行符號。
1 public BufferedWriter(Writer out, int sz) { 2 super(out); 3 if (sz <= 0) 4 throw new IllegalArgumentException("Buffer size <= 0"); 5 this.out = out; 6 cb = new char[sz]; 7 nChars = sz; 8 nextChar = 0; 9 //獲取換行的符號\n \r,和方法System.getProperty("line.separator")同樣 10 lineSeparator = (String) java.security.AccessController.doPrivileged( 11 new sun.security.action.GetPropertyAction("line.separator")); 12 }
1 public void write(int c) throws IOException { 2 synchronized (lock) { 3 //判斷是否有輸出流 4 ensureOpen(); 5 //若是緩存數組寫滿了,就flush數組。 6 if (nextChar >= nChars) 7 flushBuffer(); 8 //將內容寫入緩存數組中 9 cb[nextChar++] = (char) c; 10 } 11 } 12 //flush,這個方法就知道flush的重要性, 13 void flushBuffer() throws IOException { 14 synchronized (lock) { 15 ensureOpen(); 16 if (nextChar == 0) 17 return; 18 //將數據寫入流 19 out.write(cb, 0, nextChar); 20 nextChar = 0; 21 } 22 }
用的比較多的是寫字符串。就是將字符串轉變成字符數組。
1 public void write(String s, int off, int len) throws IOException { 2 synchronized (lock) { 3 ensureOpen(); 4 5 int b = off, t = off + len; 6 while (b < t) { 7 int d = min(nChars - nextChar, t - b); 8 //將字符串轉爲字符數組 9 s.getChars(b, b + d, cb, nextChar); 10 //寫入緩存數組 11 b += d; 12 nextChar += d; 13 if (nextChar >= nChars) 14 //若是寫滿了,就寫入流中。 15 flushBuffer(); 16 } 17 } 18 }
1 public void newLine() throws IOException { 2 //一樣寫到緩存數組裏 3 write(lineSeparator); 4 }
1 public void flush() throws IOException { 2 synchronized (lock) { 3 flushBuffer(); 4 out.flush(); 5 } 6 }
1 public void close() throws IOException { 2 synchronized (lock) { 3 if (out == null) { 4 return; 5 } 6 try { 7 //最後還釋放了一次。不過沒有執行flush方法,因此在close前仍是要執行一次flush! 8 flushBuffer(); 9 } finally { 10 out.close(); 11 out = null; 12 cb = null; 13 } 14 } 15 }
這是兩個適配器類,目的就是爲了轉換字節與字符。 實現上,使用了sun.nio.cs包中的StreamDecoder(StreamEncoder),而這兩個類都是使用java.nio.charset中的Charset(提供Unicode字符與相應字節的對應關係),以及CharsetDecoder,CharsetEncoder,ok到此打住,編碼解碼問題之後再說。。。