Java中IO流基本介绍(7)——BufferedReader和BufferedWriter

1 基本概括

2 主要介绍

2.1 高效流BufferedReader和BufferedWriter的优势

提高效率:为了提高字符流读写的效率,引入了缓冲机制,进行字符批量的读写,提高了单个字符读写的效率。BufferedReader用于加快读取字符的速度,BufferedWriter用于加快写入的速度。

简化了操作。

2.2 BufferedReader 缓冲区大小

BufferedReader和BufferedWriter类各拥有8192个字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并放满缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。

2.3 ReusableBufferedReader:可重用的 BufferedReader

标准 Java 的弱点之一BufferedReader是它只能使用一次。一旦你关闭它,它就不再可用。如果需要读取大量文件或网络流,则必须为要读取的每个文件或网络流创建一个新的 BufferedReader。这意味着您正在创建一个新对象,更重要的是,创建一个新的 char 数组,该数组用作 BufferedReader 内的缓冲区。如果读取的文件或流的数量很高,并且它们被快速读取,这会给 Java 垃圾收集器带来压力。

另一种方法是创建一个可重用的 BufferedReader ,您可以在其中替换基础 source Reader,因此可以重用 BufferedReader 及其内部字节数组缓冲区。为了省去您的麻烦,我创建了这样一个ReusableBufferedReader,并在本教程的后面包含了它的代码。首先,我想向您展示如何使用它ReusableBufferedReader。

//构造一个ReusableBufferedReader(2M的内存缓存区)
ReusableBufferedReader reusableBufferedReader = new ReusableBufferedReader(new char[1024 * 1024]);
//创建来源
FileReader reader = new FileReader("/mydata/somefile.txt"); 
reusableBufferedReader.setSource(reader);
使用完后,ReusableBufferedReader您需要关闭它。
关闭它只会关闭底层源Reader。关闭 a 后,ReusableBufferedReader 您可以再次使用它,只需Reader在其上设置新源即可。
这是重用 a 的样子ReusableBufferedReader:
reusableBufferedReader.setSource(new FileReader("/mydata/file-1.txt")); 
//从 ReusableBufferedReader 读取数据
reusableBufferedReader.close(); 
reusableBufferedReader.setSource(new FileReader("/mydata/file-1.txt")); 
//从 ReusableBufferedReader 读取数据
reusableBufferedReader.close();

2.4 readLine()方法的原理和newLine()的底层原理

readLine():无论是读一行,获取读取多个字符,其最终都是在硬盘上一个一个的读取。所以最终使用的还是read()方法一次读一个的方法。

newLine():根据系统来决定换行符

注意 不同的操作系统中换行符是不同的

* window:Windows系统里面,每行结尾是 回车+换行(CR+LF),即“\r\n”;

* linux:Unix系统里,每行结尾只有 换行CR,即“\n”;

* mac:Mac系统里,每行结尾是 回车CR 即'\r'。

2.5 System.in

从标准输入流System.in中直接读取使用者输入时,使用者每输入一个字符,System.in就读取一个字符。为了能一次读取一行使用者的输入,使用了BufferedReader来对使用者输入的字符进行缓冲。readLine()方法会在读取到使用者的换行字符时,再一次将整行字符串传入。

System.in是一个位流,为了转换为字符流,可使用InputStreamReader为其进行字符转换,然后再使用BufferedReader为其增加缓冲功能。

3 简单用例

3.1 进行文件的拷贝

@Test
public void IOTest01() throws Exception{
    BufferedReader br = new BufferedReader(new FileReader("D:\\javaio\test01.txt"));
    BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\javaio\\test02.txt"));
    //		char[] cbuf = new char[1024];
    //		int len;
    //		while((len=br.read(cbuf)) != -1){
    //			bw.write(cbuf, 0, len);
    //		}
    //BufferedReader提供了readLine方法,可以不再使用字节读取方式
   String readline;
    while((readline = br.readLine()) != null){
    	bw.write(readline);
     	bw.newLine();
    }
    bw.close();
    br.close();
}

3.2 将文件追加到文件

@Test
public void IOTest2() throws Exception{
    BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\test.txt",true));
    bw.write("我是测试用例!");
    bw.close();
}

3.3 读取文件内容

@Test
public void IOTest3() throws Exception{
    BufferedReader br = new BufferedReader(new FileReader("D:\\test.txt"));
    String readline;
    while((readline = br.readLine()) != null){
    	System.out.println(readline);
    }
    br.close();
}

4 源码

4.1 BufferedWriter源码分析

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.2 BufferedReader 源码分析

package com.chy.io.original.code;

import java.io.IOException;

/**
 * 为底层字符输入流添加字符缓冲cb数组。提高效率
 */

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; 

  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;
		}
  }
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有鹿如溪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值