上一篇博客中,测试分析了FileReader&FileWriter,和BufferWriter&BufferReader之间的性能对比。仅仅只是简单分析。现在我基于源码的角度,来分析BufferReader与FileReader的区别。

首先在构造函数上

// FileReader构造函数
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
//BufferReader构造函数
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);//defaultCharBufferSize = 8192,默认缓冲区大小
}

因为BufferedReader是对FileReader或者InputStreamReader进行包装,而FileReader的父类是InputStreamReader,所以两者的交集就是InputStreamReader,换句话说二者在构造时,都调用了InputStreamReader的构造函数:

//InputStreamReader的构造函数
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}

InputStreamReader还有其它几个重载构造函数,但是它们有一个共性,全都调用了sd = StreamDecoder.forInputStreamReader()方法。用它可以获得一个StreamDecoder对象。

再看后面的源码:

//InputStreamReader
public int read() throws IOException {
return sd.read();
}
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}

可见InputStreamReader中的read()和read(cbuf, offset, length)操作,实际上都是在StreamDecoder中进行。下面分析一下read()和read(cbuf, offset, length)两种情况下FileReader和BufferReader的区别。

read()

逐个读入字符

FileReader没有重写read()方法,所以相当于直接调用StreamDecoder中的read()。

而BufferReader重写了read(),如下:

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

可以看到read()调用了一个fill()函数。其实精髓地方就在这个fill(),它的作用就是填充输入缓冲区。代码如下:

/**
* Fills the input buffer, taking the mark into account if it is valid.
*/
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()在循环中调用了in.read(cb, dst, cb.length - dst),这实际上就是调用InputStreamReader中的read(cbuf, offset, length),即调用StreamDecoder中的read(cbuf, offset, length)。

所以,即便是一次读入一个字符,BufferReader依然是走char[]模式,性能比直接调用read()要好。

read(cbuf, offset, length)

一次读入一个char数组

注意我前一个博客中的代码是这样写的:read(cbuf),没有后面两个参数,这不用惊讶,实际上就是调用了read(cbuf, offset, length),因为在顶级父类Reader中,有如下代码:

public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}

但是注意,read(cbuf, offset, length)方法在Reader中是抽象方法,这个方法在StreamDecoder中实现。

根据源码,FileReader没有重写read(cbuf, offset, length)方法,所以相当于调用StreamDecoder中的read(cbuf, offset, length);BufferReader重写了,但也使用了fill()方法进行填充,实质上也是使用了StreamDecoder中的read(cbuf, offset, length)。

所以,使用FileReader一次读入一个char数组时,只要字符数组的大小设置合理,可以达到BufferReader的缓冲区效果。这样就解释了上篇博客测试结果。

简单总结:

  • InputStreamReader构造函数会创建StreamDecoder对象,在该对象中进行输入操作;
  • BufferReader,不管是一次读入一个字符,还是一次一个数组,都会调用StreamDecoder中的read(cbuf, offset, length),即都使用了缓冲区;
  • FileReader,逐个读取就调用read(),一次一个数组就调用read(cbuf, offset, length)。

其它

StreamDecoder

StreamDecoder的源码分析可参见这个博客,分析得很好:

JAVA基础知识之StreamDecoder流

我这里就不再赘述,只提StreamDecoder中几个关键的方法:

  • read0():逐个读入字符的方法;
  • read(char cbuf[], int offset, int length):调用implRead(cbuf, off, off + len);
  • readBytes():读入底层输入的字节流;
  • implRead():将char[]包装成字符缓冲区,调用readBytes()将数据写入字节缓冲区,然后将字节缓冲区中的字节转码成字符存入字符缓冲区。

为啥都说OutputStreamWriter和InputStreamReader是字节流和字符流的桥梁,这里貌似能窥见一斑。

FileWriter与BufferWriter

输出流的分析我这里就不深入写了,跟输入流大同小异,总结成以下三点:

  • 类似的,OutputStreamWriter在构造函数中创建StreamEncoder对象;
  • FileWriter依然不重写各种write方法;
  • BufferWriter重写了write()和write(cbuf, off, len)方法,实际上两者原理类似,都是将字符或者char数组写入buffer,buffer满了再flush写入文件,然后再继续写入buffer循环往复直到结束(char[]写入buffer,实际上就是用System.arraycopy把一个char[]复制进另一个char[];当然还有一种把string写入char[]的方法,getChars);

【java I/O流总结】基于源码比较FileReader和BufferReader的更多相关文章

  1. [Java并发] AQS抽象队列同步器源码解析--锁获取过程

    要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...

  2. 【转载】深度解读 java 线程池设计思想及源码实现

    总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...

  3. Java并发指南12:深度解读 java 线程池设计思想及源码实现

    ​深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...

  4. 转:微信开发之使用java获取签名signature(贴源码,附工程)

    微信开发之使用java获取签名signature(贴源码,附工程) 标签: 微信signature获取签名 2015-12-29 22:15 6954人阅读 评论(3) 收藏 举报  分类: 微信开发 ...

  5. Java禁止浏览器有缓存的源码

    Java禁止浏览器有缓存的源码 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.Filter ...

  6. 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)

    缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...

  7. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  8. java集合树状结构及源码

    java集合树状结构及源码 最近一直想看一下java集合的源码,毕竟平时用的比较多,但总是感觉是跟着习惯new出来一个对象,比如ArrayList,HashMap等等,所以就简单的看了一下,了解了一下 ...

  9. 基于源码编译安装openssh

     最近的,openssl/openssh等相继漏洞的暴露,让暴露在公网的linux.沦陷为肉鸡的正营... 没办法,还是升级版本... 00.openssh简介 OpenSSH 是一组安全远程的连接工 ...

随机推荐

  1. 数据分析交互工具jupyter notebook需要密码登陆解决办法

    想要做数据分析,交互可视化工具jupyter notebook是必不可少的,但是在安装和使用其时候总是会出现各种各样的问题,本文针对notebook启动需要密码的问题进行解决. 首先看一下启动jupy ...

  2. python 爬虫保存文件的几种方法

    import os os.makedirs('./img/', exist_ok=True) IMAGE_URL = "https://morvanzhou.github.io/static ...

  3. 【基础篇】hexo博客搭建教程

    [基础篇]搭建hexo博客(一) 作者:Huanhao bilibili:Mrhuanhao 前言 你是否想拥有属于自己的博客?你是否无奈与自己不会写网站而烦恼? 不要担心,本系列教程将会实现你白嫖的 ...

  4. win10 64位 安装JDK1.8

    win10 64位 jdk1.8 配置Java环境,是否安装JRE. 一.安装得有JRE JDK和JRE分别安装再不同的文件夹下 新建:JAVA_HOME 值:JDK的安装路径 新建:CLASSPAT ...

  5. Grafana使用总结

    最近工作需求学习了下grafana,根据创建的几个dashboard简要记录下创建过程. 本次使用了grafana做可视化展示,data source使用的rds是postgresql和时序数据库in ...

  6. PDF 相关操作

    去年一年偷了下懒, 博客写了一点就没写了, 还好一些大的flag完成了.  花了半年的空余时间, 培养了一门兴趣爱好.   自己在为人处世上还是不够圆滑啊, 也难怪.   自己当初选择走技术这条路的初 ...

  7. ORACLE数据库实现主键自增

    ORACLE数据库是甲骨文公司的一款关系数据库管理系统. 实现主键自动增长需要四个步骤: 去看 创建表格 去看 创建自增序列 去看 创建触发器 去看 插入测试 1.创建表格(必须有主键) -- 创建学 ...

  8. 微信小程序状态管理工具 JStore

    微信小程序状态管理工具 JStore 闲着没事做,就想着给微信小程序写一个状态管理工具,名叫 JStore,这个状态管理工具是仿照 vuex 的几个方法来写的,所以有 vuex 的基础同学很容易理解. ...

  9. 前端每日实战:55# 视频演示如何用纯 CSS 创作一个太阳、地球、月亮的运转模型

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/RJjQYY 可交互视频 此视频是可 ...

  10. Java中文件上传路径与路径修改相关问题(tomcat8.0+eclipse)

    1.普通文件上传的路径: 通过getRealPath获取相关路径 String photoFolder =request.getServletContext().getRealPath("u ...