BufferedWriter和BufferedReader分别是字符缓冲输出流和字符缓冲输入流,为底层字符流提供了缓冲的功能,底层字符流读取字符或者写入字符时,会频繁与硬盘进行交互,导致读取效率很低.缓冲流的作用就是硬盘中的数据读取到内存,再从内存中一次性读取多个数据.提高了读取的速度.根据jdk的api文档介绍,缓冲流的推荐使用方式.
1.构造方法
public BufferedWriter(Writer out) {}
public BufferedWriter(Writer out, int sz) {}
2.内部变量
private Writer out;
private char cb[];
private int nChars, nextChar;
private static int defaultCharBufferSize = 8192;
private String lineSeparator;
3.内部方法
private void ensureOpen(){}
public void write(int c){}
public void write(char cbuf[], int off, int len){}
public void write(String s, int off, int len){}
public void newLine() {}
public void flush(){}
public void close() {}
1.构造方法
public BufferedReader(Reader in, int sz) {}
public BufferedReader(Reader in) {}
2.内部变量
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;
private boolean skipLF = false;
private boolean markedSkipLF = false;
private static int defaultCharBufferSize = 8192;
private static int defaultExpectedLineLength = 80;
3.内部方法
private void ensureOpen()
private void fill()
public int read()
public int read(char cbuf[], int off, int len)
public String readLine()
public long skip(long n)
public boolean ready()
public boolean markSupported()
public void mark(int readAheadLimit)
public void reset()
public void close()
public Stream lines()
public class BufferedDemo {
public static void main(String[] args) throws IOException {
testBufferedWriter();
testBufferedReader();
}
//a test of BufferedWriter
private static void testBufferedWriter() throws IOException{
char[] cbuf = new char[] {'h','e','l','l','o','w','o','r','l','d'};
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\java.txt"));
bw.write("1234567890");
bw.write(cbuf, 0, 8);
bw.write('z');
bw.newLine(); //调用newline相当于write('\n')('\n'是系统换行符)
bw.write("bufferedDemo", 2, 5);
bw.close();
}
//a test of BufferedReader
private static void testBufferedReader() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\java.txt"));
int read = br.read();
System.out.println("单个字符------"+(char)read);
char[] cbuf = new char[1024];
br.read(cbuf, 0, 10);
System.out.println("字符数组----------"+new String(cbuf));
if(br.markSupported()) {
br.mark(100);
}
String readLine = br.readLine(); //调用readLine()读取的是下一个读取字符位置到换行符之前所有的字符.
System.out.println("读取一行-----------"+readLine);
br.reset();
System.out.println("调用reset()方法-------------"+(char)br.read());
br.skip(5);
System.out.println("跳过5个字符位置-------------"+(char)br.read());
br.close();
}
}
运行结果:
testBufferedWriter运行结果:
1234567890helloworz
ffere
testBufferedReader运行结果:
单个字符------1
字符数组----------234567890h
读取一行-----------elloworz
调用reset()方法-------------e
跳过5个字符位置-------------r
1.BufferedWriter源码
public class BufferedWriter extends Writer {
//底层字符输出流
private Writer out;
//缓冲区为字符数组cb
private char cb[];
//nChars是缓冲区的大小.
//nextChar是缓冲区中下一个要读取字符的位置索引.
private int nChars, nextChar;
//缓冲区默认的大小为了8192个字符
private static int defaultCharBufferSize = 8192;
//换行符,在创建缓冲字符输出流时赋值
private String lineSeparator;
//创建一个底层字符输出流为out,缓冲区大小为默认的8192个字符的缓冲字符输出流.
public BufferedWriter(Writer out) {
this(out, defaultCharBufferSize);
}
//创建一个底层字符输出流为out,缓冲区大小为sz的缓冲字符出输出流.
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 = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
//确保底层字符输出流没有关闭
private void ensureOpen() throws IOException {
if (out == null)
throw new IOException("Stream closed");
}
//刷新缓冲区中,将缓冲区数据写到底层字符输出流中.
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar == 0)
return;
//将缓冲区中的字符写到底层字符输出流中.
out.write(cb, 0, nextChar);
nextChar = 0;
}
}
//将单个字符c写到缓冲区中.
public void write(int c) throws IOException {
synchronized (lock) {
ensureOpen();
//缓冲区没有剩余位置,刷新缓冲区,再将字符c写到缓冲区.
if (nextChar >= nChars)
flushBuffer();
cb[nextChar++] = (char) c;
}
}
//取a和b中的较小值
private int min(int a, int b) {
if (a < b) return a;
return b;
}
//将字符数组cbuf中off位置开始,最多len个字符写到缓冲区
//缓冲区大小与要写入字符数比较.
//a.要写入字符数多于缓冲区大小,需要先刷新缓冲区,然后写入
//b.要写入字符数少于缓冲区大小,需要先判断剩余可写入的字符数,实际写入缓冲区的字符数要看缓冲区剩余位置.
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) {
//刷新缓冲区.直接将字符数组cbuf里面数据写到底层字符输出流.
flushBuffer();
out.write(cbuf, off, len);
return;
}
//如要写入缓冲区的数据长度小于缓冲区的大小
int b = off, t = off + len;
while (b < t) {
//剩余可写入字符长度和要写入字符的长度取较小的值.
int d = min(nChars - nextChar, t - b);
//将字符数组cbuf中b位置开始,复制d个字符到缓冲区cb中nextChar位置开始.
System.arraycopy(cbuf, b, cb, nextChar, d);
b += d;
nextChar += d;
//缓冲区没有剩余位置,刷新缓冲区.
if (nextChar >= nChars)
flushBuffer();
}
}
}
//将字符串s中,off位置开始,len个字符写到缓冲区.len为负数时,不会有字符写到缓冲区.
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中b开始到b+d之间的字符写到缓冲区cb中,从nextChar位置开始
s.getChars(b, b + d, cb, nextChar);
b += d;
nextChar += d;
//没有剩余位置,刷新缓冲区
if (nextChar >= nChars)
flushBuffer();
}
}
}
//写入一个换行符,换行符是系统定义的,不一定是单一'\n'符号.
public void newLine() throws IOException {
write(lineSeparator);
}
//刷新流,调用此方法会将缓冲区字符强制写出
public void flush() throws IOException {
synchronized (lock) {
flushBuffer();
out.flush();
}
}
//关闭流,释放相关资源
@SuppressWarnings("try")
public void close() throws IOException {
synchronized (lock) {
if (out == null) {
return;
}
try (Writer w = out) {
flushBuffer();
} finally {
out = null;
cb = null;
}
}
}
2.BufferedReader源码
public class BufferedReader extends Reader {
//底层字符输入流
private Reader in;
//字符缓冲输入流中的缓冲区
private char cb[];
//nchars表示的是缓冲区的里面有效字符数.
//nextChar表示缓冲区中下一个读取字符的索引.
private int nChars, nextChar;
//表示标记失效
private static final int INVALIDATED = -2;
//没有标记
private static final int UNMARKED = -1;
//缓冲区中标记的位置初始化设置为-1.
private int markedChar = UNMARKED;
//用于标记之后,任需保留标记的情况下,从缓冲区可读取字符的最大值,超过此限制,标记将会失效.
private int readAheadLimit = 0;
//是否跳过换行符(lf,line feed)
private boolean skipLF = false;
//标记的情况下,是否跳过换行符.
private boolean markedSkipLF = false;
//缓冲区默认的大小为8192个字符.
private static int defaultCharBufferSize = 8192;
//默认的每行字符的大小为80个字符.
private static int defaultExpectedLineLength = 80;
//有参构造方法,创建的是一个指定了缓冲区大小sz的字符缓冲输入流.
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
//初始化,下一个可读取的字符和有效字符数都为0
nextChar = nChars = 0;
}
//有参构造方法,创建的是缓冲区大小为默认的8192个字符的字符缓冲输入流.
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
//用于确保底层字符输入流没有关闭.
private void ensureOpen() throws IOException {
if (in == null)
throw new IOException("Stream closed");
}
//填充缓冲区,共存在四种情况.
//1.没有标记的情况下,从底层字符输入流读取数据将填充缓冲区中0-buffer.length之间位置.
//2.有标记的情况下,用于标记失效的限制值,此值与标记长度(下一个读取字符的位置-标记的位置,即标记位置开始读取的字符数)作比较.
//a.如果从标记位置开始读取字符超过限制值readAheadLimit,标记将会失效.从底层字符输入流读取数据将填充缓冲区中0-buffer.length位置.
//b.如果从标记位置开始读取的字符没有超过限制值readAheadLimit,
//第一种,readAheadLimitbuffer.length(缓冲区大小)情况下,仍会保留标记位置之后的字符.
private void fill() throws IOException {
int dst;
//没有标记,调用fill()方法,将填充缓冲区中0-cb.length之间的位置.
if (markedChar <= UNMARKED) {
dst = 0;
} else {
//delta表示标记之后读取的字符,是读取下一个字符位置与标记位置markedChar的差值.
int delta = nextChar - markedChar;
//超过限制值readAheadLimit,标记失效,调用fill()方法,将填充缓冲区中0-cb.length之间的位置.
if (delta >= readAheadLimit) {
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
} else {
//如果限制值小于cb.length值,保留标记之后的字符.
if (readAheadLimit <= cb.length) {
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
//调用fill()将会填充缓冲区中delta-cb.length之间位置.
dst = delta;
} else {
//限制值readAheadLimit超过了cb.length值,缓冲区扩容成readAheadLimit大小
char ncb[] = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
}
//标记没有失效的情况下,会保存标记之后字符.
//所以下一个要读取字符的位置nextChar和有效字符数nChars都赋值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;
}
}
//读取单个字符,字符十进制范围是0-65535之间(十六进制0x00-0xffff),返回-1表示已达到文件末尾.
public int read() throws IOException {
synchronized (lock) {
//确保底层字符输入流没有关闭
ensureOpen();
for (;;) {
//缓冲区中没有剩余位置,调用fill()方法填充缓冲区.
if (nextChar >= nChars) {
fill();
//调用fill()填充后,数据未更新,返回-1.
if (nextChar >= nChars)
return -1;
}
//skipLF为true情况下,跳过换行符
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++];
}
}
}
//从缓冲区中读取最多len个字符到cbuf字符数组中,off是字符数组cbuf开始位置.
//要读取的字符长度len,剩余可读取的字符长度n,
//a.如果剩余字符数多于要读取的字符len,那么实际可读取的字符长度为len
//b.如果剩余字符数少于要读取的字符长度len,那么实际只能读取剩余的字符数.
private int read1(char[] cbuf, int off, int len) throws IOException {
if (nextChar >= nChars) {
//如果读取字符长度超过缓冲区cb.length大小,并且没有标记和跳过换行符情况下,直接从底层输入流中读取len个字符
if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
return in.read(cbuf, off, len);
}
//是否调用fill()进行填充
fill();
}
//填充如果没有可读取的字符,返回-1.
if (nextChar >= nChars) return -1;
if (skipLF) {
skipLF = false;
//跳过换行符'\n',继续读取
if (cb[nextChar] == '\n') {
nextChar++;
//如果没有剩余可读取的字符,调用fill()方法填充.
if (nextChar >= nChars)
fill();
//填充后缓冲区的数据没有变化,表明文件到达末尾,返回-1
if (nextChar >= nChars)
return -1;
}
}
//取要读取字符的长度len与剩余可读取字符的长度中较小值,
int n = Math.min(len, nChars - nextChar);
System.arraycopy(cb, nextChar, cbuf, off, n);
nextChar += n;
return n;
}
//将缓冲区中的数据最多len个字符读取到cbuf字符数组中,off是cbuf开始位置.
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;
}
//调用read1()方法读取字符
int n = read1(cbuf, off, len);
//n<=0,没有读取到字符.
if (n <= 0) return n;
//读取的字符len超过了n个字符
while ((n < len) && in.ready()) {
int n1 = read1(cbuf, off + n, len - n);
if (n1 <= 0) break;
n += n1;
}
return n;
}
}
//读取文本的一行数据,一行结束标志是换行符('\n'),回车符号('\r')或者是回车+换行符
//此方法读取一行的内容时,读取的是实际是下一个读取字符的位置到换行符之间的所有字符.
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null;
int startChar;
synchronized (lock) {
ensureOpen();
//ignoreLF,表示下个'\n'将会被跳过
boolean omitLF = ignoreLF || skipLF;
bufferLoop:
for (;;) {
//缓冲区没有剩余可读字符,调用fill()填充
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;
//可以跳过字符'\n',并且下一个读取的字符就是'\n',nextChar++,即从下一行可以读取
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false;
charLoop:
//从nextChar(下一个读取字节的位置)到换行符(一行结束)之间还有多少个字符
//i的最终值就是nextChar+换行符之前可读字符数
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
//跳出此循环
break charLoop;
}
}
//缓冲区中从nextChar位置开始读取字符
startChar = nextChar;
//即下一次读取的位置是从i位置开始,将i赋值给nextChar,下面有nextChar++,即跳过换行符.
nextChar = i;
if (eol) {
String str;
if (s == null) {
//将缓冲区中startChar位置开始,到换行符之前所有的字符转换成字符串.
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);
}
}
}
//读取文本的一行数据,行结束标志是换行符('\n'),回车符号('\r')或者是回车+换行符
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) {
//没有可读取的字符,调用fill()进行填充
if (nextChar >= nChars)
fill();
//填充完之后数据没有变化,说明文件已经到达末尾
if (nextChar >= nChars) /* EOF */
break;
//跳过换行符
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
}
}
//有效字符数-下一个可读字符位置=剩余可读取字符d
long d = nChars - nextChar;
//如果跳过的字符数r小于等于剩余可读取字符d,将下一个读取字符位置加r
if (r <= d) {
nextChar += r;
r = 0;
break;
}
//如果跳过字符数r大于剩余可取字符d,那么最多跳过字符只能是剩余可读取字符d.
//将下一个读取字符位置置为nchars(有效字符末尾)
else {
r -= d;
nextChar = nChars;
}
}
return n - r;
}
}
//缓冲区有剩余可读取的字符,或者底层字符流准备可读,返回true.
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen();
//跳过换行符
if (skipLF) {
//如果没有剩余可读取的字符.调用fill()填充缓冲区
if (nextChar >= nChars && in.ready()) {
fill();
}
//有剩余可读取的字符,跳过'\n'
if (nextChar < nChars) {
if (cb[nextChar] == '\n')
nextChar++;
skipLF = false;
}
}
//返回缓冲区中是否有剩余的字符,或者底层字符输入流可读
return (nextChar < nChars) || in.ready();
}
}
//是否支持标记
public boolean markSupported() {
return true;
}
//标记当前位置,调用reset()方法将会重置到当前位置.
//readAheadLimit表示的是保留标记的情况下,可读取字符的上限值.超过此值,调用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;
}
}
//调用reset()方法,将会将当前重置到最后一次调用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;
try {
in.close();
} finally {
in = null;
cb = null;
}
}
}
//jdk1.8中新添加的方法,按照系统换行符,迭代每一个行的内容.得到是缓冲区中所有的字符的流.
public Stream lines() {
Iterator iter = new Iterator() {
String nextLine = null;
@Override
public boolean hasNext() {
if (nextLine != null) {
return true;
} else {
try {
nextLine = readLine();
return (nextLine != null);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
@Override
public String next() {
if (nextLine != null || hasNext()) {
String line = nextLine;
nextLine = null;
return line;
} else {
throw new NoSuchElementException();
}
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
}
}
对于BufferedReader流fill()方法,缓冲区中没有可读取的字符时,将会调用此方法填充缓冲区.总结分析以下几种情况:
先了解一下一些重要变量的含义:
1.没有标记的情况下,从底层字符输入流读取数据将填充缓冲区中0~buffer.length之间位置.
2.有标记的情况下,用于标记失效的限制值,此值与标记长度(下一个读取字符的位置-标记的位置,即标记位置开始读取的字符数)作比较.
a.如果从标记位置开始读取字符超过限制值readAheadLimit,标记将会失效.从底层字符输入流读取数据将填充缓冲区中0~buffer.length位置.(如下,nextChar-markedChar超过readAheadLimit,标记会失效).
b.如果从标记位置开始读取的字符没有超过限制值readAheadLimit. 关于BufferedWriter流中有写入单个字符,字符串以及字符数组等方法,源码不难分析.而BufferedReader流中读取有读取单个字节,读取到字符数组,读取一行数据等方法,此外还有标记mark()和重置方法reset().作为缓冲字符流,提供的主要功能还是对底层字符流进行缓冲.提高读取文件的效率.
第一种,readAheadLimit
第二种,readAheadLimit>buffer.length(缓冲区大小)情况下,会将缓冲进行扩容到readAheadLimit大小,且会保留标记位置之后的字符.(如下,将会扩容到readAheadLimit,仍旧会保存markedChar到nextChars之间的字符.下次填充从nextChar位置开始)总结