1. 程式人生 > >Java_io體系之BufferedWriter、BufferedReader簡介、走進原始碼及示例——16

Java_io體系之BufferedWriter、BufferedReader簡介、走進原始碼及示例——16

Java_io體系之BufferedWriter、BufferedReader簡介、走進原始碼及示例——16

一:BufferedWriter

1、類功能簡介:

        BufferedWriter、快取字元輸出流、他的功能是為傳入的底層字元輸出流提供快取功能、同樣當使用底層字元輸出流向目的地中寫入字元或者字元陣列時、每寫入一次就要開啟一次到目的地的連線、這樣頻繁的訪問不斷效率底下、也有可能會對儲存介質造成一定的破壞、比如當我們向磁碟中不斷的寫入位元組時、誇張一點、將一個非常大單位是G的位元組資料寫入到磁碟的指定檔案中的、沒寫入一個位元組就要開啟一次到這個磁碟的通道、這個結果無疑是恐怖的、而當我們使用BufferedWriter將底層字元輸出流、比如FileReader包裝一下之後、我們可以在程式中先將要寫入到檔案中的字元寫入到BufferedWriter的內建快取空間中、然後當達到一定數量時、一次性寫入FileReader流中、此時、FileReader就可以開啟一次通道、將這個資料塊寫入到檔案中、這樣做雖然不可能達到一次訪問就將所有資料寫入磁碟中的效果、但也大大提高了效率和減少了磁碟的訪問量!這就是其意義所在、 他的具體工作原理在這裡簡單提一下:這裡可能說的比較亂、具體可以看原始碼、不懂再回頭看看這裡、

當程式中每次將字元或者字元陣列寫入到BufferedWriter中時、都會檢查BufferedWriter中的快取字元陣列buf(buf的大小是預設的或者在建立bw時指定的、一般使用預設的就好)是否存滿、如果沒有存滿則將字元寫入到buf中、如果存滿、則呼叫底層的writer(char[] b, int off, int len)將buf中的所有字元一次性寫入到底層out中、如果寫入的是字元陣列、如果buf中已滿則同上面滿的時候的處理、如果能夠存下寫入的字元陣列、則存入buf中、如果存不下、並且要寫入buf的字元個數小於buf的長度、則將buf中所有字元寫入到out中、然後將要寫入的字元存放到buf中(從下標0開始存放)、如果要寫入out中的字元超過buf的長度、則直接寫入out中、

2、BufferedWriter  API簡介:

        A:關鍵字

    private Writer out;		 底層字元輸出流

   
    private char cb[];		  緩衝陣列
    
    private int nChars, nextChar;		 nChars--cb的size,nextChar--cb中下一個字元的下標

    private static int defaultCharBufferSize = 8192;		 預設cb大小

    private String lineSeparator;		換行符、用於newLine方法。不同平臺具有不同的值。

        B:構造方法

    BufferedWriter(Writer out)		使用預設cb大小建立BufferedWriter  bw。
    
    BufferedWriter(Writer out, int sz)		使用預設cb大小建立BufferedWriter  bw。

        C:一般方法

    void close()		關閉此流、釋放與此流有關的資源。
    
    void flushBuffer()		將cb中快取的字元flush到底層out中、
    
    void flush()	重新整理此流、同時重新整理底層out流
    
    void newLine()		寫入一個換行符。
    
    void write(int c)		將一個單個字元寫入到cb中。
    
    void write(char cbuf[], int off, int len)	將一個從下標off開始長度為len個字元寫入cb中
    
    void write(String s, int off, int len)		將一個字串的一部分寫入cb中

3、原始碼分析

package com.chy.io.original.code;

import java.io.IOException;
import java.io.PrintWriter;


/**
 * 為字元輸出流提供緩衝功能、提高效率。可以使用指定字元緩衝陣列大小也可以使用預設字元緩衝陣列大小。
 */

public class BufferedWriter extends Writer {

	//底層字元輸出流
    private Writer out;

    //緩衝陣列
    private char cb[];
    //nChars--cb中總的字元數,nextChar--cb中下一個字元的下標
    private int nChars, nextChar;

    //預設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;

    /**
     * 使用預設cb大小建立BufferedWriter  bw。
     */
    public BufferedWriter(Writer out) {
    	this(out, defaultCharBufferSize);
    }

    /**
     * 使用指定cb大小建立br、初始化相關欄位
     */
    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"));
    }

    /** 檢測底層字元輸出流是否關閉*/
    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;
		}
    }

    /**
     * 將一個單個字元寫入到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;
    }

    /**
     * 將一個從下標off開始長度為len個字元寫入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中現有的字元和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();
		    }
		}
    }

    /**
     * 將一個字串的一部分寫入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();
	    }
	}
    }

    /**
     * 寫入一個換行符。
     */
    public void newLine() throws IOException {
    	write(lineSeparator);
    }

    /**
     * 重新整理此流、同時重新整理底層out流
     */
    public void flush() throws IOException {
		synchronized (lock) {
		    flushBuffer();
		    out.flush();
		}
    }
    /**
     * 關閉此流、釋放與此流有關的資源。
     */
    public void close() throws IOException {
		synchronized (lock) {
		    if (out == null) {
			return;
		    }
		    try {
		        flushBuffer();
		    } finally {
		        out.close();
		        out = null;
		        cb = null;
		    }
		}
    }
}


4、例項演示:

            與下面的BufferedReader結合使用實現字元型別的檔案的拷貝。

二:BufferedReader

1、類功能簡介:

        緩衝字元輸入流、他的功能是為傳入的底層字元輸入流提供緩衝功能、他會通過底層字元輸入流(in)中的字元讀取到自己的buffer中(內建快取字元陣列)、然後程式呼叫BufferedReader的read方法將buffer中的字元讀取到程式中、當buffer中的字元被讀取完之後、BufferedReader會從in中讀取下一個資料塊到buffer中供程式讀取、直到in中資料被讀取完畢、這樣做的好處一是提高了讀取的效率、二是減少了開啟儲存介質的連線次數、詳細的原因下面BufferedWriter有說到。其有個關鍵的方法fill()就是每當buffer中資料被讀取完之後從in中將資料填充到buffer中、程式從記憶體中讀取資料的速度是從磁碟中讀取的十倍!這是一個很恐怖的效率的提升、同時我們也不能無禁止的指定BufferedReader的buffer大小、畢竟、一次性讀取in中耗時較長、二是記憶體價格相對昂貴、我們能做的就是儘量在其中找到合理點。一般也不用我們費這個心、建立BufferedReader時使用buffer的預設大小就好。

2、BufferedReader  API簡介:

        A:構造方法

    BufferedReader(Reader in, int sz)		根據指定大小和底層字元輸入流建立BufferedReader。br
    
    BufferedReader(Reader in)		使用預設大小建立底層輸出流的緩衝流
 

        B:一般方法

    void close()	關閉此流、釋放與此流有關的所有資源
    
    void mark(int readAheadLimit)	標記此流此時的位置
    
    boolean markSupported()		判斷此流是否支援標記
    
    void reset()	重置in被最後一次mark的位置
    
    boolean ready()		判斷此流是否可以讀取字元
    
    int read()		讀取單個字元、以整數形式返回。如果讀到in的結尾則返回-1。
    
    int read(char[] cbuf, int off, int len)	將in中len個字元讀取到cbuf從下標off開始長度len中
    
    String readLine()	讀取一行
    
    long skip(long n)		丟棄in中n個字元


3、原始碼分析

package com.chy.io.original.code;

import java.io.IOException;

/**
 * 為底層字元輸入流新增字元緩衝cb陣列。提高效率
 * @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;

    /**
     * 根據指定大小和底層字元輸入流建立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;
    }

    /**
     * 使用預設大小建立底層輸出流的緩衝流
     */
    public BufferedReader(Reader in) {
    	this(in, defaultCharBufferSize);
    }

    /** 檢測底層字元輸入流in是否關閉 */
    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;
		}
    }

    /**
     * 讀取單個字元、以整數形式返回。如果讀到in的結尾則返回-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中len個字元讀取到cbuf從下標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中len個字元讀取到cbuf從下標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個字元
     */
    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();
		}
    }

    /**
     * 判斷此流是否支援標記
     */
    public boolean markSupported() {
    	return true;
    }

    /**
     * 標記此流此時的位置、當呼叫reset方法失效前最多允許讀取readAheadLimit個字元。
     */
    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的位置。即下一個字元從被最後一次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;
		}
    }
}

4、例項演示:

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 {
	/**
	 * 這裡對這兩個類的測試比較簡單、就是對檔案字元流進行包裝、實現檔案拷貝
	 * 有興趣的可以測試一下效率、、偷個懶、、可無視
	 */
	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);
		}
		//不要忘記重新整理和關閉流、否則一方面資源沒有及時釋放、另一方面有可能照成資料丟失
		br.close();
		bw.flush();
		bw.close();
	}
}

總結:

        對於BufferedReader、BufferedWriter、本質就是為底層字元輸入輸出流新增緩衝功能、先將底層流中的要讀取或者要寫入的資料先以一次讀取一組的形式來講資料讀取或者寫入到buffer中、再對buffer進行操作、這樣不但效率、還能節省資源。最後、在程式中、出於效率的考慮、也應為低階流使用這兩個類進行裝飾一下、而不是直接拿著流直接上、覺得能實現就行。