Java 的字节流文件读取(二)
接着上篇文章,我们继续来学习 Java 中的字节流操作。
装饰者缓冲流 BufferedInput/OutputStream
装饰者流其实是基于一种设计模式「装饰者模式」而实现的一种文件 IO 流,而我们的缓冲流只是其中的一种,我们一起来看看。
在这之前,我们使用的文件读写流 FileInputStream 和 FileOutputStream 都是一个字节一个字节的从磁盘读取或写入,非常耗时。
而我们的缓冲流可以预先从磁盘一次性读出指定容量的字节数到内存中,之后的读取操作将直接从内存中读取,提高效率。下面我们一起看看缓冲流的具体实现情况:
依然先以 BufferedInputStream 为例,我们简单提一下它的几个核心属性:
- private static int DEFAULT_BUFFER_SIZE = 8192;
- protected volatile byte buf[];
- private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
- protected int count;
- protected int pos;
- protected int markpos = -1;
- protected int marklimit;
buf 就是用于缓冲读的字节数组,它的值将随着流的读取而不停的被填充,继而后续的读操作可以直接基于这个缓冲数组。
DEFAULT_BUFFER_SIZE 规定了默认缓冲区的大小,即 buf 的数组长度。MAX_BUFFER_SIZE 指明了缓冲区的上限。
count 指向缓冲数组中最后一个有效字节索引后一位。pos 指向下一个待读取的字节索引位置。
markpos 和 marklimit 用于重复读操作。
接着我们看看 BufferedInputStream 的几个示例构造器:
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
整体上来说,前者只需要传入一个「被装饰」的 InputStream 实例,并使用默认大小的缓冲区。后者则可以显式指明缓冲区的大小。
除此之外,super(in) 会将这个 InputStream 实例保存进父类 FilterInputStream 的 in 属性字段中,并且所有实际的磁盘读操作都由这个 InputStream 实例发出。
下面我们来看最重要的读操作以及缓冲区是如何被填充的。
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
这个方法想必大家已经很熟悉了,从流中读取下一个字节并返回,但细节上的实现还是稍稍有些不同。
count 指向了缓冲数组中有效字节索引后一位置处,pos 指向下一个待读取的字节索引位置。理论上 pos 是不可能大于 count 的,最多等于。
如果 pos 等于 count,那说明缓冲数组中所有有效字节都已经被读取过了,此时即需要丢弃缓冲区中那些「无用」的数据,从磁盘重新加载一批新数据填充缓冲区。
而事实上,fill 方法就是做的这个事情,它的代码比较多,就不带大家去解析了,你理解了它的作用,想必分析它的实现也是容易的。
如果 fill 方法调用之后,pos 依然 等于 count,那么说明 InputStream 实例并没有从流中读取出任何数据,也即文件流中无数据可读。关于这一点,参见 fill 方法 246 行。
总的来说,如果成功填充了缓冲区,那么我们的 read 方法将直接从缓冲区取出一个字节返回给调用者。
public synchronized int read(byte b[], int off, int len){
//.....
}
这个方法也是「熟人」了,不再多余的解释了,实现是类似的。
skip 方法用于跳过指定长度的字节数进行文件流的继续读取:
public synchronized long skip(long n){
//.....
}
注意一点的是,skip 方法尽量去跳过 n 个字节,但不保证一定跳过 n 个字节,方法返回的是实际跳过的字节数。如果缓冲数组中剩余可用字节数小于 n,那么最终将跳过缓冲数组中实际可跳过的字节数。
最后要说一说这个 close 方法:
public void close() throws IOException {
byte[] buffer;
while ( (buffer = buf) != null) {
if (bufUpdater.compareAndSet(this, buffer, null)) {
InputStream input = in;
in = null;
if (input != null)
input.close();
return;
}
// Else retry in case a new buf was CASed in fill()
}
}
close 方法将赋空「被装饰者」流,并调用它的 close 方法释放相关资源,最终也会清空缓冲数组所占用的内存空间。
BufferedInputStream 提供了读缓冲能力,而 BufferedOutputStream 则提供了写缓冲能力,即内存的写操作并不会立马更新到磁盘,暂时保存在缓冲区,待缓冲区满时一并写入。
protected byte buf[];
protected int count;
buf 代表了内部缓冲区,count 表示缓冲区中实际数据容量,即 buf 中有效字节数,而不是 buf 数组长度。
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
一样的实现思路,必须提供的是一个 OutputStream 输出流实例,也可以选择性指明缓冲区大小。
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
写方法将首先检查缓冲区是否还能容纳本次写操作,如果不能将发起一次磁盘写操作,将缓冲区数据全部写入磁盘文件,否则将优先写入缓冲区。
当然,BufferedOutputStream 也提供了 flush 方法向外提供接口,也即不一定非要等到缓冲区满了才向磁盘写数据,你也可以显式的调用该方法让它清空缓冲区并更新磁盘文件。
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
关于缓冲流,核心内容介绍如上,这是一种能够显著提升效率的流,通过它,能够减少磁盘访问次数,提升程序执行效率。
有关对象序列化流 ObjectInput/OutputStream 以及基于基本类型的装饰者流 DataInput/OutputStream 我们这里暂时不做讨论。待到我们学习序列化的时候,再回头讨论这两个字节流。
文章中的所有代码、图片、文件都云存储在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。
Java 的字节流文件读取(二)的更多相关文章
- Java 的字节流文件读取(一)
上篇文章我们介绍了抽象化磁盘文件的 File 类型,它仅仅用于抽象化描述一个磁盘文件或目录,却不具备访问和修改一个文件内容的能力. Java 的 IO 流就是用于读写文件内容的一种设计,它能完成将磁盘 ...
- java中的文件读取和文件写出:如何从一个文件中获取内容以及如何向一个文件中写入内容
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.Fi ...
- Java学习-017-EXCEL 文件读取实例源代码
众所周知,EXCEL 也是软件测试开发过程中,常用的数据文件导入导出时的类型文件之一,此文主要讲述如何通过 EXCEL 文件中 Sheet 的索引(index)或者 Sheet 名称获取文件中对应 S ...
- Java学习-019-Properties 文件读取实例源代码
在这几天的学习过程中,有开发的朋友告知我,每个编程语言基本都有相应的配置文件支持类,像 Python 编程语言中支持的 ini 文件及其对应的配置文件读取类 ConfigParse,通过这个类,用户可 ...
- Java学习-016-CSV 文件读取实例源代码
上文(CSV文件写入)讲述了日常自动化测试过程中将测试数据写入 CSV 文件的源码,此文主要讲述如何从 CSV 文件获取测试过程中所需的参数化数据.敬请各位小主参阅,若有不足之处,敬请大神指正,不胜感 ...
- Java解决大文件读取的内存问题以及文件流的比较
Java解决大文件读取的内存问题以及文件流的比较 传统方式 读取文件的方式一般是是从内存中读取,官方提供了几种方式,如BufferedReader, 以及InputStream 系列的,也有封装好的如 ...
- Java之properties文件读取
1.工程结构 2.ConfigFileTest.java package com.configfile; import java.io.IOException; import java.io.Inpu ...
- Java开发中文件读取方式总结
JAVA开发中,免不了要读文件操作,读取文件,首先就需要获取文件的路径. 路径分为绝对路径和相对路径. 在文件系统中,绝对路径都是以盘符开始的,例如C:\abc\1.txt. 什么是相对路径呢?相对路 ...
- JAVA 中的文件读取
1. InputStream / OutputStream处理字节流抽象类:所有输入.输出(内存)类的超类,一般使用 FileInputStream / FileOutputStream 输出字符 u ...
随机推荐
- c++11 线程池学习笔记 (一) 任务队列
学习内容来自一下地址 http://www.cnblogs.com/qicosmos/p/4772486.html github https://github.com/qicosmos/cosmos ...
- 设置DataGridView中表头颜色
默认的DataGridView表头颜色实在不是太好看,想设置下,上google搜了一通, 都说这样设置 this.dataGridView1.ColumnHeadersDefaultCellStyle ...
- 《C#从现象到本质》读书笔记(七)第9章 泛型
<C#从现象到本质>读书笔记(七)第9章 泛型 泛型的三大好处:类型安全,增强性能(避免装箱和拆箱),代码复用. 泛型方法是传入的参数至少有一个类型为T(尚未制定的类型,根据微软的命名规则 ...
- 《Linux就该这么学》第二天课程
秦时明月经典语录:很多人被命运安排,而我安排命运.——卫庄 今天介绍了VM 虚拟机的安装以及Linux系统的安装,还讲解了Linux内核 RPM:降低软件的安装难度 源代码+安装规则→将程序源代码与安 ...
- jQuery三级联动
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- vue全局路由守卫beforeEach
在main.js里使用方法 router.beforeEach((to,from,next)=>{}) to,是将要跳转的路由, from,是离开的路由 next是个方法,判断to.path 或 ...
- Tarjan算法(图论)(转)
有点烦人的算法 贴个算法解析合辑先:https://blog.csdn.net/hurmishine/article/details/75248876
- Codeforces gym102152 K.Subarrays OR
传送:http://codeforces.com/gym/102152/problem/K 题意:给定$n(n\le10^5)$个数$a_i(a_i\le10^9)$,对于任一个子数组中的数进行或操作 ...
- 美团2018年CodeM大赛-初赛B轮 C题低位值
试题链接:https://www.nowcoder.com/acm/contest/151/C 定义lowbit(x) =x&(-x),即2^(p-1) (其中p为x的二进制表示中,从右向左数 ...
- Android OpenGL ES 开发(N): OpenGL ES 2.0 机型兼容问题整理
在使用OpenGL ES做开发的时候,发现不是所有机型对OpenGL的代码都兼容的那么好,同样的代码在某些机型上总是会出现问题,但是在其他手机上就是好的.下面是本人总结的OpengGL 兼容问题: 一 ...