Java BufferedWriter BufferedReader 源碼分析
一:BufferedWriter
1、類(lèi)功能簡(jiǎn)介:
BufferedWriter、緩存字符輸出流、他的功能是為傳入的底層字符輸出流提供緩存功能、同樣當(dāng)使用底層字符輸出流向目的地中寫(xiě)入字符或者字符數(shù)組時(shí)、每寫(xiě)入一次就要打開(kāi)一次到目的地的連接、這樣頻繁的訪問(wèn)不斷效率底下、也有可能會(huì)對(duì)存儲(chǔ)介質(zhì)造成一定的破壞、比如當(dāng)我們向磁盤(pán)中不斷的寫(xiě)入字節(jié)時(shí)、夸張一點(diǎn)、將一個(gè)非常大單位是G的字節(jié)數(shù)據(jù)寫(xiě)入到磁盤(pán)的指定文件中的、沒(méi)寫(xiě)入一個(gè)字節(jié)就要打開(kāi)一次到這個(gè)磁盤(pán)的通道、這個(gè)結(jié)果無(wú)疑是恐怖的、而當(dāng)我們使用BufferedWriter將底層字符輸出流、比如FileReader包裝一下之后、我們可以在程序中先將要寫(xiě)入到文件中的字符寫(xiě)入到BufferedWriter的內(nèi)置緩存空間中、然后當(dāng)達(dá)到一定數(shù)量時(shí)、一次性寫(xiě)入FileReader流中、此時(shí)、FileReader就可以打開(kāi)一次通道、將這個(gè)數(shù)據(jù)塊寫(xiě)入到文件中、這樣做雖然不可能達(dá)到一次訪問(wèn)就將所有數(shù)據(jù)寫(xiě)入磁盤(pán)中的效果、但也大大提高了效率和減少了磁盤(pán)的訪問(wèn)量!這就是其意義所在、 他的具體工作原理在這里簡(jiǎn)單提一下:這里可能說(shuō)的比較亂、具體可以看源碼、不懂再回頭看看這里、當(dāng)程序中每次將字符或者字符數(shù)組寫(xiě)入到BufferedWriter中時(shí)、都會(huì)檢查BufferedWriter中的緩存字符數(shù)組buf(buf的大小是默認(rèn)的或者在創(chuàng)建bw時(shí)指定的、一般使用默認(rèn)的就好)是否存滿、如果沒(méi)有存滿則將字符寫(xiě)入到buf中、如果存滿、則調(diào)用底層的writer(char[] b, int off, int len)將buf中的所有字符一次性寫(xiě)入到底層out中、如果寫(xiě)入的是字符數(shù)組、如果buf中已滿則同上面滿的時(shí)候的處理、如果能夠存下寫(xiě)入的字符數(shù)組、則存入buf中、如果存不下、并且要寫(xiě)入buf的字符個(gè)數(shù)小于buf的長(zhǎng)度、則將buf中所有字符寫(xiě)入到out中、然后將要寫(xiě)入的字符存放到buf中(從下標(biāo)0開(kāi)始存放)、如果要寫(xiě)入out中的字符超過(guò)buf的長(zhǎng)度、則直接寫(xiě)入out中、
2、BufferedWriter API簡(jiǎn)介:
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方法。不同平臺(tái)具有不同的值。
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() 寫(xiě)入一個(gè)換行符。
void write(int c) 將一個(gè)單個(gè)字符寫(xiě)入到cb中。
void write(char cbuf[], int off, int len) 將一個(gè)從下標(biāo)off開(kāi)始長(zhǎng)度為len個(gè)字符寫(xiě)入cb中
void write(String s, int off, int len) 將一個(gè)字符串的一部分寫(xiě)入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方法。不同平臺(tái)具有不同的值。
*/
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;
//獲取不同平臺(tái)下的換行符表示方式。
lineSeparator = (String) java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
/** 檢測(cè)底層字符輸出流是否關(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è)字符寫(xiě)入到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開(kāi)始長(zhǎng)度為len個(gè)字符寫(xiě)入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的長(zhǎng)度、那么就直接將cb中現(xiàn)有的字符和cbuf中的字符寫(xiě)入out中、
* 而不是寫(xiě)入cb、再寫(xiě)入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è)字符串的一部分寫(xiě)入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();
}
}
}
/**
* 寫(xiě)入一個(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)字符類(lèi)型的文件的拷貝。
二:BufferedReader
1、類(lèi)功能簡(jiǎn)介:
緩沖字符輸入流、他的功能是為傳入的底層字符輸入流提供緩沖功能、他會(huì)通過(guò)底層字符輸入流(in)中的字符讀取到自己的buffer中(內(nèi)置緩存字符數(shù)組)、然后程序調(diào)用BufferedReader的read方法將buffer中的字符讀取到程序中、當(dāng)buffer中的字符被讀取完之后、BufferedReader會(huì)從in中讀取下一個(gè)數(shù)據(jù)塊到buffer中供程序讀取、直到in中數(shù)據(jù)被讀取完畢、這樣做的好處一是提高了讀取的效率、二是減少了打開(kāi)存儲(chǔ)介質(zhì)的連接次數(shù)、詳細(xì)的原因下面BufferedWriter有說(shuō)到。其有個(gè)關(guān)鍵的方法fill()就是每當(dāng)buffer中數(shù)據(jù)被讀取完之后從in中將數(shù)據(jù)填充到buffer中、程序從內(nèi)存中讀取數(shù)據(jù)的速度是從磁盤(pán)中讀取的十倍!這是一個(gè)很恐怖的效率的提升、同時(shí)我們也不能無(wú)禁止的指定BufferedReader的buffer大小、畢竟、一次性讀取in中耗時(shí)較長(zhǎng)、二是內(nèi)存價(jià)格相對(duì)昂貴、我們能做的就是盡量在其中找到合理點(diǎn)。一般也不用我們費(fèi)這個(gè)心、創(chuàng)建BufferedReader時(shí)使用buffer的默認(rèn)大小就好。
2、BufferedReader API簡(jiǎn)介:
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開(kāi)始長(zhǎng)度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);
}
/** 檢測(cè)底層字符輸入流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開(kāi)始長(zhǎng)度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開(kāi)始長(zhǎng)度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的位置開(kāi)始讀取。
*/
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 {
/**
* 這里對(duì)這兩個(gè)類(lèi)的測(cè)試比較簡(jiǎn)單、就是對(duì)文件字符流進(jìn)行包裝、實(shí)現(xiàn)文件拷貝
* 有興趣的可以測(cè)試一下效率、、偷個(gè)懶、、可無(wú)視
*/
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)閉流、否則一方面資源沒(méi)有及時(shí)釋放、另一方面有可能照成數(shù)據(jù)丟失
br.close();
bw.flush();
bw.close();
}
}
總結(jié):
對(duì)于BufferedReader、BufferedWriter、本質(zhì)就是為底層字符輸入輸出流添加緩沖功能、先將底層流中的要讀取或者要寫(xiě)入的數(shù)據(jù)先以一次讀取一組的形式來(lái)講數(shù)據(jù)讀取或者寫(xiě)入到buffer中、再對(duì)buffer進(jìn)行操作、這樣不但效率、還能節(jié)省資源。最后、在程序中、出于效率的考慮、也應(yīng)為低級(jí)流使用這兩個(gè)類(lèi)進(jìn)行裝飾一下、而不是直接拿著流直接上、覺(jué)得能實(shí)現(xiàn)就行。
相關(guān)文章
SpringBoot 返回Json實(shí)體類(lèi)屬性大小寫(xiě)的解決
這篇文章主要介紹了SpringBoot 返回Json實(shí)體類(lèi)屬性大小寫(xiě)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
逆轉(zhuǎn)交替合并兩個(gè)鏈表的解析與實(shí)現(xiàn)
本篇文章主要介紹了將兩個(gè)鏈表逆轉(zhuǎn)交替合并的實(shí)現(xiàn)思路與方法,需要的朋友可以參考下2015-07-07
在SpringBoot中實(shí)現(xiàn)一個(gè)訂單號(hào)生成系統(tǒng)的示例代碼
在Spring Boot中設(shè)計(jì)一個(gè)訂單號(hào)生成系統(tǒng),主要考慮到生成的訂單號(hào)需要滿足的幾個(gè)要求:唯一性、可擴(kuò)展性、以及可能的業(yè)務(wù)相關(guān)性,本文給大家介紹了幾種常見(jiàn)的解決方案及相應(yīng)的示例代碼,需要的朋友可以參考下2024-02-02
java并發(fā)請(qǐng)求下數(shù)據(jù)插入重復(fù)問(wèn)題的解決方法
現(xiàn)在遇到一個(gè)項(xiàng)目,移動(dòng)設(shè)備存儲(chǔ)數(shù)據(jù),然后一起上傳,那就出現(xiàn)了許多重復(fù)數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于java并發(fā)請(qǐng)求下數(shù)據(jù)插入重復(fù)問(wèn)題的解決方法,需要的朋友可以參考下2021-11-11
java并發(fā)編程包JUC線程同步CyclicBarrier語(yǔ)法示例
這篇文章主要為大家介紹了java并發(fā)編程工具包JUC線程同步CyclicBarrier語(yǔ)法使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
Java hashCode原理以及與equals()區(qū)別聯(lián)系詳解
在 Java 應(yīng)用程序執(zhí)行期間,在同一對(duì)象上多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是對(duì)象上 equals 比較中所用的信息沒(méi)有被修改。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無(wú)需保持一致2022-11-11

