程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java io學習(二十二) BufferedReader(字符緩沖輸入流)

java io學習(二十二) 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 源碼分析(基於jdk1.7.40)

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)”大於“緩沖的長度”;
					// 查看本欄目
			
		
		

說明:

要想讀懂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 == 0);
    if (n > 0) {
        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 測試程序
 *
 * @author skywang
 */
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, 0, 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

來源:http://www.cnblogs.com/skywang12345/p/io_23.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved