Java BufferedWriter BufferedReader 源碼分析
一:BufferedWriter
1、類功能簡介:
BufferedWriter、緩存字符輸出流、他的功能是為傳入的底層字符輸出流提供緩存功能、同樣當(dāng)使用底層字符輸出流向目的地中寫入字符或者字符數(shù)組時(shí)、每寫入一次就要打開一次到目的地的連接、這樣頻繁的訪問不斷效率底下、也有可能會對存儲介質(zhì)造成一定的破壞、比如當(dāng)我們向磁盤中不斷的寫入字節(jié)時(shí)、夸張一點(diǎn)、將一個(gè)非常大單位是G的字節(jié)數(shù)據(jù)寫入到磁盤的指定文件中的、沒寫入一個(gè)字節(jié)就要打開一次到這個(gè)磁盤的通道、這個(gè)結(jié)果無疑是恐怖的、而當(dāng)我們使用BufferedWriter將底層字符輸出流、比如FileReader包裝一下之后、我們可以在程序中先將要寫入到文件中的字符寫入到BufferedWriter的內(nèi)置緩存空間中、然后當(dāng)達(dá)到一定數(shù)量時(shí)、一次性寫入FileReader流中、此時(shí)、FileReader就可以打開一次通道、將這個(gè)數(shù)據(jù)塊寫入到文件中、這樣做雖然不可能達(dá)到一次訪問就將所有數(shù)據(jù)寫入磁盤中的效果、但也大大提高了效率和減少了磁盤的訪問量!這就是其意義所在、 他的具體工作原理在這里簡單提一下:這里可能說的比較亂、具體可以看源碼、不懂再回頭看看這里、當(dāng)程序中每次將字符或者字符數(shù)組寫入到BufferedWriter中時(shí)、都會檢查BufferedWriter中的緩存字符數(shù)組buf(buf的大小是默認(rèn)的或者在創(chuàng)建bw時(shí)指定的、一般使用默認(rèn)的就好)是否存滿、如果沒有存滿則將字符寫入到buf中、如果存滿、則調(diào)用底層的writer(char[] b, int off, int len)將buf中的所有字符一次性寫入到底層out中、如果寫入的是字符數(shù)組、如果buf中已滿則同上面滿的時(shí)候的處理、如果能夠存下寫入的字符數(shù)組、則存入buf中、如果存不下、并且要寫入buf的字符個(gè)數(shù)小于buf的長度、則將buf中所有字符寫入到out中、然后將要寫入的字符存放到buf中(從下標(biāo)0開始存放)、如果要寫入out中的字符超過buf的長度、則直接寫入out中、
2、BufferedWriter API簡介:
A:關(guān)鍵字 private Writer out; 底層字符輸出流 private char cb[]; 緩沖數(shù)組 private int nChars, nextChar; nChars--cb的size,nextChar--cb中下一個(gè)字符的下標(biāo) private static int defaultCharBufferSize = 8192; 默認(rèn)cb大小 private String lineSeparator; 換行符、用于newLine方法。不同平臺具有不同的值。 B:構(gòu)造方法 BufferedWriter(Writer out) 使用默認(rèn)cb大小創(chuàng)建BufferedWriter bw。 BufferedWriter(Writer out, int sz) 使用默認(rèn)cb大小創(chuàng)建BufferedWriter bw。 C:一般方法 void close() 關(guān)閉此流、釋放與此流有關(guān)的資源。 void flushBuffer() 將cb中緩存的字符flush到底層out中、 void flush() 刷新此流、同時(shí)刷新底層out流 void newLine() 寫入一個(gè)換行符。 void write(int c) 將一個(gè)單個(gè)字符寫入到cb中。 void write(char cbuf[], int off, int len) 將一個(gè)從下標(biāo)off開始長度為len個(gè)字符寫入cb中 void write(String s, int off, int len) 將一個(gè)字符串的一部分寫入cb中
3、源碼分析
package com.chy.io.original.code; import java.io.IOException; import java.io.PrintWriter; /** * 為字符輸出流提供緩沖功能、提高效率??梢允褂弥付ㄗ址彌_數(shù)組大小也可以使用默認(rèn)字符緩沖數(shù)組大小。 */ public class BufferedWriter extends Writer { //底層字符輸出流 private Writer out; //緩沖數(shù)組 private char cb[]; //nChars--cb中總的字符數(shù),nextChar--cb中下一個(gè)字符的下標(biāo) private int nChars, nextChar; //默認(rèn)cb大小 private static int defaultCharBufferSize = 8192; /** * Line separator string. This is the value of the line.separator * property at the moment that the stream was created. * 換行符、用于newLine方法。不同平臺具有不同的值。 */ private String lineSeparator; /** * 使用默認(rèn)cb大小創(chuàng)建BufferedWriter bw。 */ public BufferedWriter(Writer out) { this(out, defaultCharBufferSize); } /** * 使用指定cb大小創(chuàng)建br、初始化相關(guān)字段 */ public BufferedWriter(Writer out, int sz) { super(out); if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0"); this.out = out; cb = new char[sz]; nChars = sz; nextChar = 0; //獲取不同平臺下的換行符表示方式。 lineSeparator = (String) java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("line.separator")); } /** 檢測底層字符輸出流是否關(guān)閉*/ private void ensureOpen() throws IOException { if (out == null) throw new IOException("Stream closed"); } /** * 將cb中緩存的字符flush到底層out中、但是不flush底層out中的字符。 * 并且將cb清空。 */ void flushBuffer() throws IOException { synchronized (lock) { ensureOpen(); if (nextChar == 0) return; out.write(cb, 0, nextChar); nextChar = 0; } } /** * 將一個(gè)單個(gè)字符寫入到cb中。 */ public void write(int c) throws IOException { synchronized (lock) { ensureOpen(); if (nextChar >= nChars) flushBuffer(); cb[nextChar++] = (char) c; } } /** * Our own little min method, to avoid loading java.lang.Math if we've run * out of file descriptors and we're trying to print a stack trace. */ private int min(int a, int b) { if (a < b) return a; return b; } /** * 將一個(gè)從下標(biāo)off開始長度為len個(gè)字符寫入cb中 */ public void write(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } if (len >= nChars) { /* 如果len大于cb的長度、那么就直接將cb中現(xiàn)有的字符和cbuf中的字符寫入out中、 * 而不是寫入cb、再寫入out中 。 */ flushBuffer(); out.write(cbuf, off, len); return; } int b = off, t = off + len; while (b < t) { int d = min(nChars - nextChar, t - b); System.arraycopy(cbuf, b, cb, nextChar, d); b += d; nextChar += d; if (nextChar >= nChars) flushBuffer(); } } } /** * 將一個(gè)字符串的一部分寫入cb中 */ public void write(String s, int off, int len) throws IOException { synchronized (lock) { ensureOpen(); int b = off, t = off + len; while (b < t) { int d = min(nChars - nextChar, t - b); s.getChars(b, b + d, cb, nextChar); b += d; nextChar += d; if (nextChar >= nChars) flushBuffer(); } } } /** * 寫入一個(gè)換行符。 */ public void newLine() throws IOException { write(lineSeparator); } /** * 刷新此流、同時(shí)刷新底層out流 */ public void flush() throws IOException { synchronized (lock) { flushBuffer(); out.flush(); } } /** * 關(guān)閉此流、釋放與此流有關(guān)的資源。 */ public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } } }
4、實(shí)例演示:與下面的BufferedReader結(jié)合使用實(shí)現(xiàn)字符類型的文件的拷貝。
二:BufferedReader
1、類功能簡介:
緩沖字符輸入流、他的功能是為傳入的底層字符輸入流提供緩沖功能、他會通過底層字符輸入流(in)中的字符讀取到自己的buffer中(內(nèi)置緩存字符數(shù)組)、然后程序調(diào)用BufferedReader的read方法將buffer中的字符讀取到程序中、當(dāng)buffer中的字符被讀取完之后、BufferedReader會從in中讀取下一個(gè)數(shù)據(jù)塊到buffer中供程序讀取、直到in中數(shù)據(jù)被讀取完畢、這樣做的好處一是提高了讀取的效率、二是減少了打開存儲介質(zhì)的連接次數(shù)、詳細(xì)的原因下面BufferedWriter有說到。其有個(gè)關(guān)鍵的方法fill()就是每當(dāng)buffer中數(shù)據(jù)被讀取完之后從in中將數(shù)據(jù)填充到buffer中、程序從內(nèi)存中讀取數(shù)據(jù)的速度是從磁盤中讀取的十倍!這是一個(gè)很恐怖的效率的提升、同時(shí)我們也不能無禁止的指定BufferedReader的buffer大小、畢竟、一次性讀取in中耗時(shí)較長、二是內(nèi)存價(jià)格相對昂貴、我們能做的就是盡量在其中找到合理點(diǎn)。一般也不用我們費(fèi)這個(gè)心、創(chuàng)建BufferedReader時(shí)使用buffer的默認(rèn)大小就好。
2、BufferedReader API簡介:
A:構(gòu)造方法 BufferedReader(Reader in, int sz) 根據(jù)指定大小和底層字符輸入流創(chuàng)建BufferedReader。br BufferedReader(Reader in) 使用默認(rèn)大小創(chuàng)建底層輸出流的緩沖流 B:一般方法 void close() 關(guān)閉此流、釋放與此流有關(guān)的所有資源 void mark(int readAheadLimit) 標(biāo)記此流此時(shí)的位置 boolean markSupported() 判斷此流是否支持標(biāo)記 void reset() 重置in被最后一次mark的位置 boolean ready() 判斷此流是否可以讀取字符 int read() 讀取單個(gè)字符、以整數(shù)形式返回。如果讀到in的結(jié)尾則返回-1。 int read(char[] cbuf, int off, int len) 將in中l(wèi)en個(gè)字符讀取到cbuf從下標(biāo)off開始長度len中 String readLine() 讀取一行 long skip(long n) 丟棄in中n個(gè)字符
3、源碼分析
package com.chy.io.original.code; import java.io.IOException; /** * 為底層字符輸入流添加字符緩沖cb數(shù)組。提高效率 * @version 1.1, 13/11/17 * @author andyChen */ public class BufferedReader extends Reader { private Reader in; private char cb[]; private int nChars, nextChar; private static final int INVALIDATED = -2; private static final int UNMARKED = -1; private int markedChar = UNMARKED; private int readAheadLimit = 0; /* Valid only when markedChar > 0 */ /** If the next character is a line feed, skip it */ private boolean skipLF = false; /** The skipLF flag when the mark was set */ private boolean markedSkipLF = false; private static int defaultCharBufferSize = 8192; private static int defaultExpectedLineLength = 80; /** * 根據(jù)指定大小和底層字符輸入流創(chuàng)建BufferedReader。br */ public BufferedReader(Reader in, int sz) { super(in); if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0"); this.in = in; cb = new char[sz]; nextChar = nChars = 0; } /** * 使用默認(rèn)大小創(chuàng)建底層輸出流的緩沖流 */ public BufferedReader(Reader in) { this(in, defaultCharBufferSize); } /** 檢測底層字符輸入流in是否關(guān)閉 */ private void ensureOpen() throws IOException { if (in == null) throw new IOException("Stream closed"); } /** * 填充cb。 */ private void fill() throws IOException { int dst; if (markedChar <= UNMARKED) { /* No mark */ dst = 0; } else { /* Marked */ int delta = nextChar - markedChar; if (delta >= readAheadLimit) { /* Gone past read-ahead limit: Invalidate mark */ markedChar = INVALIDATED; readAheadLimit = 0; dst = 0; } else { if (readAheadLimit <= cb.length) { /* Shuffle in the current buffer */ System.arraycopy(cb, markedChar, cb, 0, delta); markedChar = 0; dst = delta; } else { /* Reallocate buffer to accommodate read-ahead limit */ char ncb[] = new char[readAheadLimit]; System.arraycopy(cb, markedChar, ncb, 0, delta); cb = ncb; markedChar = 0; dst = delta; } nextChar = nChars = delta; } } int n; do { n = in.read(cb, dst, cb.length - dst); } while (n == 0); if (n > 0) { nChars = dst + n; nextChar = dst; } } /** * 讀取單個(gè)字符、以整數(shù)形式返回。如果讀到in的結(jié)尾則返回-1。 */ public int read() throws IOException { synchronized (lock) { ensureOpen(); for (;;) { if (nextChar >= nChars) { fill(); if (nextChar >= nChars) return -1; } if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; continue; } } return cb[nextChar++]; } } } /** * 將in中l(wèi)en個(gè)字符讀取到cbuf從下標(biāo)off開始長度len中 */ private int read1(char[] cbuf, int off, int len) throws IOException { if (nextChar >= nChars) { /* If the requested length is at least as large as the buffer, and if there is no mark/reset activity, and if line feeds are not being skipped, do not bother to copy the characters into the local buffer. In this way buffered streams will cascade harmlessly. */ if (len >= cb.length && markedChar <= UNMARKED && !skipLF) { return in.read(cbuf, off, len); } fill(); } if (nextChar >= nChars) return -1; if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; if (nextChar >= nChars) fill(); if (nextChar >= nChars) return -1; } } int n = Math.min(len, nChars - nextChar); System.arraycopy(cb, nextChar, cbuf, off, n); nextChar += n; return n; } /** * 將in中l(wèi)en個(gè)字符讀取到cbuf從下標(biāo)off開始長度len中 */ public int read(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int n = read1(cbuf, off, len); if (n <= 0) return n; while ((n < len) && in.ready()) { int n1 = read1(cbuf, off + n, len - n); if (n1 <= 0) break; n += n1; } return n; } } /** * 從in中讀取一行、是否忽略換行符 */ String readLine(boolean ignoreLF) throws IOException { StringBuffer s = null; int startChar; synchronized (lock) { ensureOpen(); boolean omitLF = ignoreLF || skipLF; bufferLoop: for (;;) { if (nextChar >= nChars) fill(); if (nextChar >= nChars) { /* EOF */ if (s != null && s.length() > 0) return s.toString(); else return null; } boolean eol = false; char c = 0; int i; /* Skip a leftover '\n', if necessary */ if (omitLF && (cb[nextChar] == '\n')) nextChar++; skipLF = false; omitLF = false; charLoop: for (i = nextChar; i < nChars; i++) { c = cb[i]; if ((c == '\n') || (c == '\r')) { eol = true; break charLoop; } } startChar = nextChar; nextChar = i; if (eol) { String str; if (s == null) { str = new String(cb, startChar, i - startChar); } else { s.append(cb, startChar, i - startChar); str = s.toString(); } nextChar++; if (c == '\r') { skipLF = true; } return str; } if (s == null) s = new StringBuffer(defaultExpectedLineLength); s.append(cb, startChar, i - startChar); } } } /** * 從in中讀取一行、 */ public String readLine() throws IOException { return readLine(false); } /** * 丟棄in中n個(gè)字符 */ public long skip(long n) throws IOException { if (n < 0L) { throw new IllegalArgumentException("skip value is negative"); } synchronized (lock) { ensureOpen(); long r = n; while (r > 0) { if (nextChar >= nChars) fill(); if (nextChar >= nChars) /* EOF */ break; if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; } } long d = nChars - nextChar; if (r <= d) { nextChar += r; r = 0; break; } else { r -= d; nextChar = nChars; } } return n - r; } } /** * 判斷cb中是否為空、或者底層in中是否有可讀字符。 */ public boolean ready() throws IOException { synchronized (lock) { ensureOpen(); /* * If newline needs to be skipped and the next char to be read * is a newline character, then just skip it right away. */ if (skipLF) { /* Note that in.ready() will return true if and only if the next * read on the stream will not block. */ if (nextChar >= nChars && in.ready()) { fill(); } if (nextChar < nChars) { if (cb[nextChar] == '\n') nextChar++; skipLF = false; } } return (nextChar < nChars) || in.ready(); } } /** * 判斷此流是否支持標(biāo)記 */ public boolean markSupported() { return true; } /** * 標(biāo)記此流此時(shí)的位置、當(dāng)調(diào)用reset方法失效前最多允許讀取readAheadLimit個(gè)字符。 */ public void mark(int readAheadLimit) throws IOException { if (readAheadLimit < 0) { throw new IllegalArgumentException("Read-ahead limit < 0"); } synchronized (lock) { ensureOpen(); this.readAheadLimit = readAheadLimit; markedChar = nextChar; markedSkipLF = skipLF; } } /** * 重置in被最后一次mark的位置。即下一個(gè)字符從被最后一次mark的位置開始讀取。 */ public void reset() throws IOException { synchronized (lock) { ensureOpen(); if (markedChar < 0) throw new IOException((markedChar == INVALIDATED) ? "Mark invalid" : "Stream not marked"); nextChar = markedChar; skipLF = markedSkipLF; } } //關(guān)閉此流、釋放與此流有關(guān)的所有資源 public void close() throws IOException { synchronized (lock) { if (in == null) return; in.close(); in = null; cb = null; } } }
4、實(shí)例演示:
package com.chy.io.original.test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class BufferedWriterAndBufferedReaderTest { /** * 這里對這兩個(gè)類的測試比較簡單、就是對文件字符流進(jìn)行包裝、實(shí)現(xiàn)文件拷貝 * 有興趣的可以測試一下效率、、偷個(gè)懶、、可無視 */ public static void main(String[] args) throws IOException{ File resouceFile = new File("D:\\test.txt"); File targetFile = new File("E:\\copyOftest.txt"); BufferedReader br = new BufferedReader(new FileReader(resouceFile)); BufferedWriter bw = new BufferedWriter(new FileWriter(targetFile)); char[] cbuf = new char[1024]; int n = 0; while((n = br.read(cbuf)) != -1){ bw.write(cbuf, 0, n); } //不要忘記刷新和關(guān)閉流、否則一方面資源沒有及時(shí)釋放、另一方面有可能照成數(shù)據(jù)丟失 br.close(); bw.flush(); bw.close(); } }
總結(jié):
對于BufferedReader、BufferedWriter、本質(zhì)就是為底層字符輸入輸出流添加緩沖功能、先將底層流中的要讀取或者要寫入的數(shù)據(jù)先以一次讀取一組的形式來講數(shù)據(jù)讀取或者寫入到buffer中、再對buffer進(jìn)行操作、這樣不但效率、還能節(jié)省資源。最后、在程序中、出于效率的考慮、也應(yīng)為低級流使用這兩個(gè)類進(jìn)行裝飾一下、而不是直接拿著流直接上、覺得能實(shí)現(xiàn)就行。
相關(guān)文章
SpringBoot 返回Json實(shí)體類屬性大小寫的解決
這篇文章主要介紹了SpringBoot 返回Json實(shí)體類屬性大小寫的解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10逆轉(zhuǎn)交替合并兩個(gè)鏈表的解析與實(shí)現(xiàn)
本篇文章主要介紹了將兩個(gè)鏈表逆轉(zhuǎn)交替合并的實(shí)現(xiàn)思路與方法,需要的朋友可以參考下2015-07-07在SpringBoot中實(shí)現(xiàn)一個(gè)訂單號生成系統(tǒng)的示例代碼
在Spring Boot中設(shè)計(jì)一個(gè)訂單號生成系統(tǒng),主要考慮到生成的訂單號需要滿足的幾個(gè)要求:唯一性、可擴(kuò)展性、以及可能的業(yè)務(wù)相關(guān)性,本文給大家介紹了幾種常見的解決方案及相應(yīng)的示例代碼,需要的朋友可以參考下2024-02-02java并發(fā)請求下數(shù)據(jù)插入重復(fù)問題的解決方法
現(xiàn)在遇到一個(gè)項(xiàng)目,移動設(shè)備存儲數(shù)據(jù),然后一起上傳,那就出現(xiàn)了許多重復(fù)數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于java并發(fā)請求下數(shù)據(jù)插入重復(fù)問題的解決方法,需要的朋友可以參考下2021-11-11java并發(fā)編程包JUC線程同步CyclicBarrier語法示例
這篇文章主要為大家介紹了java并發(fā)編程工具包JUC線程同步CyclicBarrier語法使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03Java hashCode原理以及與equals()區(qū)別聯(lián)系詳解
在 Java 應(yīng)用程序執(zhí)行期間,在同一對象上多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是對象上 equals 比較中所用的信息沒有被修改。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無需保持一致2022-11-11