上一篇博客中,测试分析了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. Maven本地仓库在C盘下无法自动下载相关依赖的问题

    打开项目时用管理员权限!!!

  2. 瑞士一苹果店内iPhone突然爆炸致7人入院,iPhone还值得买吗?

    与国产手机高歌猛进态势呈现"最萌发展差"的,是以三星.苹果为代表的海外手机厂商在过去一年的疲态.数据显示,2017年小米手机产量较2016年增长幅度达76%,OPPO和vivo则分 ...

  3. Android 绘制中国地图

    最近的版本有这样一个需求: 有 3 个要素: 中国地图 高亮省区 中心显示数字 面对这样一个需求,该如何实现呢? 高德地图 因为项目是基于高德地图来做的,所以很自然而然的想到了高德.但是当查阅高德地图 ...

  4. webpack中打包拷贝静态文件CopyWebpackPlugin插件

    copyWebpackPlugin: 作用:用于webpack打包时拷贝文件的插件包 安装:npm install copyWebpackPlugin@版本号 使用:// copy custom st ...

  5. ZOJ 4109 Welcome Party

    题目链接:(https://zoj.pintia.cn/problem-sets/91827364500/problems/91827370504)(https://vjudge.net/proble ...

  6. iOS下的 Fixed BUG

    input 光标位置乱窜 固定式浮层内的输入框光标会发生偏移.即 fixed 定位的容器中输入框光标的位置显示不正确,没有正常地显示在输入框中,而是偏移到了输入框外面 可触发条件 页面body出现滚动 ...

  7. CSS+JS相应式导航菜单

    响应式导航菜单 响应式导航菜单就是当网页在其他不同视口的样式,不同的设备需要不同的样式 需要掌握的知识 - 掌握媒体查询,如果你不是很懂那就看我写的CSS响应式布局 掌握CSS重的display:no ...

  8. DvaJS入门课

    不管是Vue还是React,他们都没解决组件间的通信和数据流问题.当然,这个说法不是很准确,准确的说法是他们都没很好的处理这些问题.我们是可以用一些烂手段去解决这个问题,但是当应用比较大.数据多的时候 ...

  9. HUD Is It A Tree?!!!!!)

    这个是一个非常恶心的题,刚开始以为是POJ的题,结果发现不是,一直都是超时.上题吧 A tree is a well-known data structure that is either empty ...

  10. JS中的reduce()详解

    reduce()作为一个循环使用.接收四个参数:初始值(上一次返回值),当前元素值,当前元素下标,原数组. 应用  作为累加器使用 var a=[4,5,6,7,8] //item代表一次回调的值 初 ...