您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關 BufferedReader如何在Java項目中使用,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
BufferedReader 介紹
BufferedReader 是緩沖字符輸入流。它繼承于Reader。
BufferedReader 的作用是為其他字符輸入流添加一些緩沖功能。
BufferedReader 函數列表
BufferedReader(Reader in) BufferedReader(Reader in, int size) void close() void mark(int markLimit) boolean markSupported() int read() int read(char[] buffer, int offset, int length) String readLine() boolean ready() void reset() long skip(long charCount)
BufferedReader 源碼分析
package java.io; public class BufferedReader extends Reader { private Reader in; // 字符緩沖區 private char cb[]; // nChars 是cb緩沖區中字符的總的個數 // nextChar 是下一個要讀取的字符在cb緩沖區中的位置 private int nChars, nextChar; // 表示“標記無效”。它與UNMARKED的區別是: // (01) UNMARKED 是壓根就沒有設置過標記。 // (02) 而INVALIDATED是設置了標記,但是被標記位置太長,導致標記無效! 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 */ // skipLF(即skip Line Feed)是“是否忽略換行符”標記 private boolean skipLF = false; // 設置“標記”時,保存的skipLF的值 private boolean markedSkipLF = false; // 默認字符緩沖區大小 private static int defaultCharBufferSize = 8192; // 默認每一行的字符個數 private static int defaultExpectedLineLength = 80; // 創建“Reader”對應的BufferedReader對象,sz是BufferedReader的緩沖區大小 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; } // 創建“Reader”對應的BufferedReader對象,默認的BufferedReader緩沖區大小是8k public BufferedReader(Reader in) { this(in, defaultCharBufferSize); } // 確保“BufferedReader”是打開狀態 private void ensureOpen() throws IOException { if (in == null) throw new IOException("Stream closed"); } // 填充緩沖區函數。有以下兩種情況被調用: // (01) 緩沖區沒有數據時,通過fill()可以向緩沖區填充數據。 // (02) 緩沖區數據被讀完,需更新時,通過fill()可以更新緩沖區的數據。 private void fill() throws IOException { // dst表示“cb中填充數據的起始位置”。 int dst; if (markedChar <= UNMARKED) { // 沒有標記的情況,則設dst=0。 dst = 0; } else { // delta表示“當前標記的長度”,它等于“下一個被讀取字符的位置”減去“標記的位置”的差值; int delta = nextChar - markedChar; if (delta >= readAheadLimit) { // 若“當前標記的長度”超過了“標記上限(readAheadLimit)”, // 則丟棄標記! markedChar = INVALIDATED; readAheadLimit = 0; dst = 0; } else { if (readAheadLimit <= cb.length) { // 若“當前標記的長度”沒有超過了“標記上限(readAheadLimit)”, // 并且“標記上限(readAheadLimit)”小于/等于“緩沖的長度”; // 則先將“下一個要被讀取的位置,距離我們標記的置符的距離”間的字符保存到cb中。 System.arraycopy(cb, markedChar, cb, 0, delta); markedChar = 0; dst = delta; } else { // 若“當前標記的長度”沒有超過了“標記上限(readAheadLimit)”, // 并且“標記上限(readAheadLimit)”大于“緩沖的長度”; // 則重新設置緩沖區大小,并將“下一個要被讀取的位置,距離我們標記的置符的距離”間的字符保存到cb中。 char ncb[] = new char[readAheadLimit]; System.arraycopy(cb, markedChar, ncb, 0, delta); cb = ncb; markedChar = 0; dst = delta; } // 更新nextChar和nChars nextChar = nChars = delta; } } int n; do { // 從“in”中讀取數據,并存儲到字符數組cb中; // 從cb的dst位置開始存儲,讀取的字符個數是cb.length - dst // n是實際讀取的字符個數;若n==0(即一個也沒讀到),則繼續讀取! n = in.read(cb, dst, cb.length - dst); } while (n == 0); // 如果從“in”中讀到了數據,則設置nChars(cb中字符的數目)=dst+n, // 并且nextChar(下一個被讀取的字符的位置)=dst。 if (n > 0) { nChars = dst + n; nextChar = dst; } } // 從BufferedReader中讀取一個字符,該字符以int的方式返回 public int read() throws IOException { synchronized (lock) { ensureOpen(); for (;;) { // 若“緩沖區的數據已經被讀完”, // 則先通過fill()更新緩沖區數據 if (nextChar >= nChars) { fill(); if (nextChar >= nChars) return -1; } // 若要“忽略換行符”, // 則對下一個字符是否是換行符進行處理。 if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; continue; } } // 返回下一個字符 return cb[nextChar++]; } } } // 將緩沖區中的數據寫入到數組cbuf中。off是數組cbuf中的寫入起始位置,len是寫入長度 private int read(char[] cbuf, int off, int len) throws IOException { // 若“緩沖區的數據已經被讀完”,則更新緩沖區數據。 if (nextChar >= nChars) { if (len >= cb.length && markedChar <= UNMARKED && !skipLF) { return in.read(cbuf, off, len); } fill(); } // 若更新數據之后,沒有任何變化;則退出。 if (nextChar >= nChars) return -; // 若要“忽略換行符”,則進行相應處理 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; } // 對read()的封裝,添加了“同步處理”和“阻塞式讀取”等功能 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; } } // 讀取一行數據。ignoreLF是“是否忽略換行符” 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); } } } // 讀取一行數據。不忽略換行符 public String readLine() throws IOException { return readLine(false); } // 跳過n個字符 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; } } // “下一個字符”是否可讀 public boolean ready() throws IOException { synchronized (lock) { ensureOpen(); // 若忽略換行符為true; // 則判斷下一個符號是否是換行符,若是的話,則忽略 if (skipLF) { if (nextChar >= nChars && in.ready()) { fill(); } if (nextChar < nChars) { if (cb[nextChar] == '\n') nextChar++; skipLF = false; } } return (nextChar < nChars) || in.ready(); } } // 始終返回true。因為BufferedReader支持mark(), reset() public boolean markSupported() { return true; } // 標記當前BufferedReader的下一個要讀取位置。關于readAheadLimit的作用,參考后面的說明。 public void mark(int readAheadLimit) throws IOException { if (readAheadLimit < 0) { throw new IllegalArgumentException("Read-ahead limit < 0"); } synchronized (lock) { ensureOpen(); // 設置readAheadLimit this.readAheadLimit = readAheadLimit; // 保存下一個要讀取的位置 markedChar = nextChar; // 保存“是否忽略換行符”標記 markedSkipLF = skipLF; } } // 重置BufferedReader的下一個要讀取位置, // 將其還原到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; } } public void close() throws IOException { synchronized (lock) { if (in == null) return; in.close(); in = null; cb = null; } } }
說明:
要想讀懂BufferReader的源碼,就要先理解它的思想。BufferReader的作用是為其它Reader提供緩沖功能。創建BufferReader時,我們會通過它的構造函數指定某個Reader為參數。BufferReader會將該Reader中的數據分批讀取,每次讀取一部分到緩沖中;操作完緩沖中的這部分數據之后,再從Reader中讀取下一部分的數據。
為什么需要緩沖呢?原因很簡單,效率問題!緩沖中的數據實際上是保存在內存中,而原始數據可能是保存在硬盤或NandFlash中;而我們知道,從內存中讀取數據的速度比從硬盤讀取數據的速度至少快10倍以上。
那干嘛不干脆一次性將全部數據都讀取到緩沖中呢?第一,讀取全部的數據所需要的時間可能會很長。第二,內存價格很貴,容量不想硬盤那么大。
下面,我就BufferReader中最重要的函數fill()進行說明。其它的函數很容易理解,我就不詳細介紹了,大家可以參考源碼中的注釋進行理解。我們先看看fill()的源碼:
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; } }
根據fill()中的if...else...,我將fill()分為4種情況進行說明。
情況1:讀取完緩沖區的數據,并且緩沖區沒有被標記
執行流程如下,
(01) 其它函數調用 fill(),來更新緩沖區的數據
(02) fill() 執行代碼 if (markedChar <= UNMARKED) { ... }
為了方便分析,我們將這種情況下fill()執行的操作等價于以下代碼:
private void fill() throws IOException { int dst; if (markedChar <= UNMARKED) { /* No mark */ dst = 0; } int n; do { n = in.read(cb, dst, cb.length - dst); } while (n == 0); if (n > 0) { nChars = dst + n; nextChar = dst; } }
說明:
這種情況發生的情況是 — — Reader中有很長的數據,我們每次從中讀取一部分數據到緩沖中進行操作。每次當我們讀取完緩沖中的數據之后,并且此時BufferedReader沒有被標記;那么,就接著從Reader(BufferReader提供緩沖功能的Reader)中讀取下一部分的數據到緩沖中。
其中,判斷是否讀完緩沖區中的數據,是通過“比較nextChar和nChars之間大小”來判斷的。其中,nChars 是緩沖區中字符的總的個數,而 nextChar 是緩沖區中下一個要讀取的字符的位置。
判斷BufferedReader有沒有被標記,是通過“markedChar”來判斷的。
理解這個思想之后,我們再對這種情況下的fill()的代碼進行分析,就特別容易理解了。
(01) if (markedChar <= UNMARKED) 它的作用是判斷“BufferedReader是否被標記”。若被標記,則dst=0。
(02) in.read(cb, dst, cb.length - dst) 等價于 in.read(cb, 0, cb.length),意思是從Reader對象in中讀取cb.length個數據,并存儲到緩沖區cb中,而且從緩沖區cb的位置0開始存儲。該函數返回值等于n,也就是n表示實際讀取的字符個數。若n=0(即沒有讀取到數據),則繼續讀取,直到讀到數據為止。
(03) nChars=dst+n 等價于 nChars=n;意味著,更新緩沖區數據cb之后,設置nChars(緩沖區的數據個數)為n。
(04) nextChar=dst 等價于 nextChar=0;意味著,更新緩沖區數據cb之后,設置nextChar(緩沖區中下一個會被讀取的字符的索引值)為0。
情況2:讀取完緩沖區的數據,緩沖區的標記位置>0,并且“當前標記的長度”超過“標記上限(readAheadLimit)”
執行流程如下,
(01) 其它函數調用 fill(),來更新緩沖區的數據
(02) fill() 執行代碼 if (delta >= readAheadLimit) { ... }
為了方便分析,我們將這種情況下fill()執行的操作等價于以下代碼:
private void fill() throws IOException { int dst; if (markedChar > UNMARKED) { int delta = nextChar - markedChar; if (delta >= readAheadLimit) { markedChar = INVALIDATED; readAheadLimit = 0; dst = 0; } } int n; do { n = in.read(cb, dst, cb.length - dst); } while (n == 0); if (n > 0) { nChars = dst + n; nextChar = dst; } }
說明:
這種情況發生的情況是 — — BufferedReader中有很長的數據,我們每次從中讀取一部分數據到緩沖區中進行操作。當我們讀取完緩沖區中的數據之后,并且此時,BufferedReader存在標記時,同時,“當前標記的長度”大于“標記上限”;那么,就發生情況2。此時,我們會丟棄“標記”并更新緩沖區。
(01) delta = nextChar - markedChar;其中,delta就是“當前標記的長度”,它是“下一個被讀取字符的位置”減去“被標記的位置”的差值。
(02) if (delta >= readAheadLimit);其中,當delta >= readAheadLimit,就意味著,“當前標記的長度”>=“標記上限”。為什么要有標記上限,即readAheadLimit的值到底有何意義呢?
我們標記一個位置之后,更新緩沖區的時候,被標記的位置會被保存;當我們不停的更新緩沖區的時候,被標記的位置會被不停的放大。然后內存的容量是有效的,我們不可能不限制長度的存儲標記。所以,需要readAheadLimit來限制標記長度!
(03) in.read(cb, dst, cb.length - dst) 等價于 in.read(cb, 0, cb.length),意思是從Reader對象in中讀取cb.length個數據,并存儲到緩沖區cb中,而且從緩沖區cb的位置0開始存儲。該函數返回值等于n,也就是n表示實際讀取的字符個數。若n=0(即沒有讀取到數據),則繼續讀取,直到讀到數據為止。
(04) nChars=dst+n 等價于 nChars=n;意味著,更新緩沖區數據cb之后,設置nChars(緩沖區的數據個數)為n。
(05) nextChar=dst 等價于 nextChar=0;意味著,更新緩沖區數據cb之后,設置nextChar(緩沖區中下一個會被讀取的字符的索引值)為0。
情況3:讀取完緩沖區的數據,緩沖區的標記位置>0,“當前標記的長度”沒超過“標記上限(readAheadLimit)”,并且“標記上限(readAheadLimit)”小于/等于“緩沖的長度”;
執行流程如下,
(01) 其它函數調用 fill(),來更新緩沖區的數據
(02) fill() 執行代碼 if (readAheadLimit <= cb.length) { ... }
為了方便分析,我們將這種情況下fill()執行的操作等價于以下代碼:
private void fill() throws IOException { int dst; if (markedChar > UNMARKED) { int delta = nextChar - markedChar; if ((delta < readAheadLimit) && (readAheadLimit <= cb.length) ) { System.arraycopy(cb, markedChar, cb, 0, delta); 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; } }
說明:
這種情況發生的情況是 — — BufferedReader中有很長的數據,我們每次從中讀取一部分數據到緩沖區中進行操作。當我們讀取完緩沖區中的數據之后,并且此時,BufferedReader存在標記時,同時,“當前標記的長度”小于“標記上限”,并且“標記上限”小于/等于“緩沖區長度”;那么,就發生情況3。此時,我們保留“被標記的位置”(即,保留被標記位置開始的數據),并更新緩沖區(將新增的數據,追加到保留的數據之后)。
情況4:讀取完緩沖區的數據,緩沖區的標記位置>0,“當前標記的長度”沒超過“標記上限(readAheadLimit)”,并且“標記上限(readAheadLimit)”大于“緩沖的長度”;
執行流程如下,
(01) 其它函數調用 fill(),來更新緩沖區的數據
(02) fill() 執行代碼 else { char ncb[] = new char[readAheadLimit]; ... }
為了方便分析,我們將這種情況下fill()執行的操作等價于以下代碼:
private void fill() throws IOException { int dst; if (markedChar > UNMARKED) { int delta = nextChar - markedChar; if ((delta < readAheadLimit) && (readAheadLimit > cb.length) ) { 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 == ); if (n > ) { nChars = dst + n; nextChar = dst; } }
說明:
這種情況發生的情況是 — — BufferedReader中有很長的數據,我們每次從中讀取一部分數據到緩沖區中進行操作。當我們讀取完緩沖區中的數據之后,并且此時,BufferedReader存在標記時,同時,“當前標記的長度”小于“標記上限”,并且“標記上限”大于“緩沖區長度”;那么,就發生情況4。此時,我們要先更新緩沖區的大小,然后再保留“被標記的位置”(即,保留被標記位置開始的數據),并更新緩沖區數據(將新增的數據,追加到保留的數據之后)。
示例代碼
關于BufferedReader中API的詳細用法,參考示例代碼(BufferedReaderTest.java):
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.FileReader; import java.io.IOException; import java.io.FileNotFoundException; import java.lang.SecurityException; /** * BufferedReader 測試程序 * * */ public class BufferedReaderTest { private static final int LEN = 5; public static void main(String[] args) { testBufferedReader() ; } /** * BufferedReader的API測試函數 */ private static void testBufferedReader() { // 創建BufferedReader字符流,內容是ArrayLetters數組 try { File file = new File("bufferedreader.txt"); BufferedReader in = new BufferedReader( new FileReader(file)); // 從字符流中讀取5個字符。“abcde” for (int i=0; i<LEN; i++) { // 若能繼續讀取下一個字符,則讀取下一個字符 if (in.ready()) { // 讀取“字符流的下一個字符” int tmp = in.read(); System.out.printf("%d : %c\n", i, tmp); } } // 若“該字符流”不支持標記功能,則直接退出 if (!in.markSupported()) { System.out.println("make not supported!"); return ; } // 標記“當前索引位置”,即標記第6個位置的元素--“f” // 1024對應marklimit in.mark(1024); // 跳過22個字符。 in.skip(22); // 讀取5個字符 char[] buf = new char[LEN]; in.read(buf, 0, LEN); System.out.printf("buf=%s\n", String.valueOf(buf)); // 讀取該行剩余的數據 System.out.printf("readLine=%s\n", in.readLine()); // 重置“輸入流的索引”為mark()所標記的位置,即重置到“f”處。 in.reset(); // 從“重置后的字符流”中讀取5個字符到buf中。即讀取“fghij” in.read(buf, , LEN); System.out.printf("buf=%s\n", String.valueOf(buf)); in.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
程序中讀取的bufferedreader.txt的內容如下:
abcdefghijklmnopqrstuvwxyz 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ
運行結果:
0 : a 1 : b 2 : c 3 : d 4 : e buf=01234 readLine=56789 buf=fghij
上述就是小編為大家分享的 BufferedReader如何在Java項目中使用了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。