前面咱們已經大體分析了經常使用的字節流,如今咱們來經過分析兩個抽象類Reader和Writer來了解下字符流。java
根據JDK源碼的註釋Reader是字符輸入流的抽象接口,它的子類必須實現的方法僅有read(char[], int off, int len)、close()方法,大部分子類都會選擇重寫某些方法以提供更好的性能或者額外的功能,例如BufferedReader等。子類能夠經過重寫mark、markSuppot()和reset方法支持輸入流標記的功能。數組
如下是該類的源碼解讀:緩存
package java.io; public abstract class Reader implements Readable, Closeable { //對象local主要用於同步針對此流的操做,處於性能考慮,一個字符流可能會使用一個內部的鎖對象保護關鍵代碼 //塊(同步代碼塊),JDK推薦使用此對象而不是使用當前對象this或者使用synchronized方法 protected Object lock; //構造函數,建立一個字符流Reader,它的同步代碼塊將使用自己this對象進行同步 protected Reader() { this.lock = this; } //構造函數,它的同步代碼塊將使用方法指定的lock對象進行同步 protected Reader(Object lock) { if (lock == null) { throw new NullPointerException(); } this.lock = lock; } /** * 嘗試讀取字符到指定的字符緩衝區 * 緩衝區可用做字符的緩衝區,所作的惟一改變是put操做的結果,不對緩衝區執行翻轉或者重繞操做 */ public int read(java.nio.CharBuffer target) throws IOException { //獲取緩衝區的剩餘字符個數 int len = target.remaining(); char[] cbuf = new char[len]; //從輸入流中讀取len個字符到字符數組cbuf中,返回實際讀取的字符數 int n = read(cbuf, 0, len); //若實際讀取的字符數大於0,將實際讀取到的字符存入緩衝區target中 if (n > 0) target.put(cbuf, 0, n); //返回實際讀取的字符個數 return n; } /** * Reads a single character. This method will block until a character is * available, an I/O error occurs, or the end of the stream is reached. * * <p> Subclasses that intend to support efficient single-character input * should override this method. * * @return The character read, as an integer in the range 0 to 65535 * (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has * been reached * * @exception IOException If an I/O error occurs */ public int read() throws IOException { char cb[] = new char[1]; if (read(cb, 0, 1) == -1) return -1; else return cb[0]; } /** * 將字符輸入流中的數據讀入到cbuf,該方法會阻塞直到存在新的可讀數據,發生IO錯誤以及到達讀取終點 * 返回實際讀取的字符數,或者-1標識已經到達字符輸入流末尾 */ public int read(char cbuf[]) throws IOException { return read(cbuf, 0, cbuf.length); } /** * 從輸入流中讀取字符數據到指定的字符數組cbuf,方法指定了數組cbuf存儲輸入流字符數據的起點、和讀取的最 * 大字符數,返回實際讀取的字符個數,-1標識到達輸入流末尾 */ abstract public int read(char cbuf[], int off, int len) throws IOException; /** 保存被跳過字符數據緩存數組長度的最大限制 */ private static final int maxSkipBufferSize = 8192; /** 保存被跳過字符數據的緩存數組 ,初始化爲null須要的時候進行分配 **/ private char skipBuffer[] = null; /** * 跳過指定字符數. 該方法可能阻塞當跳過的字符中有些字符正在使用,或者發生IO錯誤,或者輸入流讀取結束 */ public long skip(long n) throws IOException { //n小於0拋出IllagalArgumentException非法參數異常 if (n < 0L) throw new IllegalArgumentException("skip value is negative"); //跳轉字符數不能超過最大限制,JDK1.8規定是8192 int nn = (int) Math.min(n, maxSkipBufferSize); synchronized (lock) { //若現有保存跳轉字符的緩衝區數組skipBuffer爲空或者長度小於指定跳轉字符數則爲他從新分配一個 //長度爲指定跳轉字符數大小的char數組 if ((skipBuffer == null) || (skipBuffer.length < nn)) skipBuffer = new char[nn]; long r = n; while (r > 0) { int nc = read(skipBuffer, 0, (int)Math.min(r, nn)); if (nc == -1) break; r -= nc; } return n - r; } } /** * 檢測當前輸入流是否可讀 * 若是下一次read不會被阻塞則返回true不然返回false,注意返回false也不是說下次read必定阻塞 */ public boolean ready() throws IOException { return false; } /** * 返回本類是否支持標記,支持返回true不支持返回false,默認實現返回false,子類若是支持標記須要重寫該 *方法 */ public boolean markSupported() { return false; } /** * 標記輸入流的當前位置.以後調用reset將會重定位到當前位置從新開始讀取.不是全部的字符輸入流都支持mark * 方法,readAheadLimit參數用於指定標記以後到能夠調用reset方法重置輸入流的字符數,當上一次標記位置 * 以後繼續讀取超過該限制的字符數據嘗試經過調用reset回滾到上次標記位置從新讀取將會失敗 * 發生IO錯誤或者該字符輸入流不支持mark操做方法拋出IOException異常 */ public void mark(int readAheadLimit) throws IOException { throw new IOException("mark() not supported"); } /** * 重置輸入流. 若是該輸入流被標記, 那麼嘗試從新定位到以前標記的位置從新讀取以後的數據. * 若是輸入流沒有標記過,那麼根據不一樣的實現類,會進行不一樣的重置處理,例如重定位當前讀取位置到輸入流初始 * 位置.並非全部的字符輸入流都支持reset方法,一些支持reset操做的可能也不支持標記操做mark. * 當輸入流不曾標記或者標記無效或者輸入流不支持reset方法或者發生其餘的IO錯誤都會拋出IO異常 */ public void reset() throws IOException { throw new IOException("reset() not supported"); } /** * 關閉輸入流釋放與之相關的系統資源,當輸入流關閉以後,調用read、ready、mark、reset或者skip方法都 *將拋出IOException異常,調用close方法關閉以前已經關閉的輸入流不會有任何影響。 */ abstract public void close() throws IOException; }
Writer是字符輸出流的抽象接口,它的子類必須實現的方法包括write(char[], int off, int len),flush()和close()方法。大部分子類都會選擇重寫某些方法已提供更好的性能或者額外的方法。app
如下是該類的源碼解讀:ide
package java.io; public abstract class Writer implements Appendable, Closeable, Flushable { /** * 字符緩衝數組用於臨時存放將要寫入到輸出流中的字符 */ private char[] writeBuffer; /** * 字符緩衝數組的容量 */ private static final int WRITE_BUFFER_SIZE = 1024; /** * 鎖對象用於同步針對該輸出流的操做.處於性能考慮,字符輸出流對象可能會使用一個鎖對象而不是對它自己加鎖去保護關鍵 * 代碼塊(同步代碼塊)。子類應該使用這個鎖對象而不是Writer對象自己或者同步方法 */ protected Object lock; /** * 構造函數。它關鍵部分的代碼(須要同步的代碼塊)將會選擇他自己做爲鎖對象進行同步 */ protected Writer() { this.lock = this; } /** * 構造函數。它的關鍵部分代碼(同步代碼塊)將使用方法指定的鎖對象進行同步 */ protected Writer(Object lock) { if (lock == null) { throw new NullPointerException(); } this.lock = lock; } /** * 寫入單個字符,要寫入的字符包含在給定整數值的低16位中,高16位被忽略 * 子類若想實現高效的單字符寫入方法可重寫 */ public void write(int c) throws IOException { synchronized (lock) { if (writeBuffer == null){ writeBuffer = new char[WRITE_BUFFER_SIZE]; } writeBuffer[0] = (char) c; write(writeBuffer, 0, 1); } } /** * 嘗試將一個字節數組寫入到輸出流中 */ public void write(char cbuf[]) throws IOException { write(cbuf, 0, cbuf.length); } /** * 嘗試將一個數組從off開始的len個字符寫入到輸出流中,可能寫入的字符數小於len個 */ abstract public void write(char cbuf[], int off, int len) throws IOException; /** * 寫入一個字符串 */ public void write(String str) throws IOException { write(str, 0, str.length()); } /** * 試圖將字符串的一部分,從off開始的len個字符寫入到輸出流中 * 嘗試寫入len個字符,但寫入的字符數可能低於len個 */ public void write(String str, int off, int len) throws IOException { synchronized (lock) { char cbuf[]; if (len <= WRITE_BUFFER_SIZE) { if (writeBuffer == null) { writeBuffer = new char[WRITE_BUFFER_SIZE]; } cbuf = writeBuffer; } else { // Don't permanently allocate very large buffers. cbuf = new char[len]; } str.getChars(off, (off + len), cbuf, 0); write(cbuf, 0, len); } } /** * 將指定的字符序列寫入到字符輸出流末尾。與調用out.write的效果徹底相同。由於調用該方法實際寫入輸出流的內容實際上是 * csq.toString方法返回的內容,因此整個字符序列可否被所有被寫進去取決於指定字符序列csq的toString方法實現, * 例如調用一個CharacterBuffer的toString方法,方法返回的內容取決於它當前的讀取位置和保存的字符個數。當csq爲 * null時會往輸入流寫入「null」四個字符 */ public Writer append(CharSequence csq) throws IOException { if (csq == null) write("null"); else write(csq.toString()); return this; } /** * 將指定字符序列的一部分(start位置開始到end位置不包括end位置字符)追加到該字符輸出流末尾, */ public Writer append(CharSequence csq, int start, int end) throws IOException { CharSequence cs = (csq == null ? "null" : csq); write(cs.subSequence(start, end).toString()); return this; } /** * 往輸出流追加指定的字符 */ public Writer append(char c) throws IOException { write(c); return this; } /** * 刷新該流的緩衝區.若是緩衝數組保存了write方法寫入的字符數據,那麼馬上將他們寫入到目標對象,目標對象多是綁定的 * 另外一個字符輸出流或者字節輸出流,該方法將會刷新綁定的全部字符和字節輸出流的緩衝區 * 若是寫入的目標是一個File對象那麼該輸出流僅保證先前寫入流的數據傳遞到操做系統進行寫入,可是不保證當即寫入物理設 * 備中 */ abstract public void flush() throws IOException; /** * 關閉當前輸出流方法,抽象方法留給子類實現, 建議關閉流以前先調用刷新函數flush刷新緩存.一旦該輸出流關閉後續調用 * write和flush方法都應該拋出一個IO異常。重複關閉一個字符輸出流不會有問題 */ abstract public void close() throws IOException; }
1)操做的對象不一樣,Reader操做的是字符,InputStream是字節;函數
2)Reader默認實現了一個Readable接口,比InputStream多提供了一個將輸入流中的字符數據讀取到指定字符緩存CharBuffer的方法(read(java.nio.CharBuffer));性能
3)Reader的close方法是抽象的子類必須實現,而InputStream的close方法不是。this
1)Write操做的是字符,OutputStream操做的是字節;spa
2)實現的接口不一樣,Writer相比OutputStream多實現了Appendable接口,提供了幾個在輸出流中寫入單個字符、字符序列的方法;操作系統
3)Writer的close、flush方法是抽象的,子類必須實現,而OutputStream的close、flush方法不是;