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 ...
随机推荐
- 16. pt-mysql-summary
pt-mysql-summary --host=192.168.100.101 --port=3306 --user=admin --password=admin \ pt-mysql-summary ...
- 服务器安装ubuntu 14.04 server,开机启动屏幕不停滚动错误WRITE SAME failed. Manually zeroing
昨天给服务器上安装了一个Ubuntu-14.04-server系统,安装完成后系统可以正常启动,但屏幕上一直滚动着一个错误,sda1:WRITE SAME failed. Manually zeroi ...
- python中global和nonlocal用法的详细说明
一.global 1.global关键字用来在函数或其他局部作用域中使用全局变量.但是如果不修改全局变量也可以不使用global关键字. gcount = 0 def global_test(): ...
- _ZNote_Qt_定时器的总结
Qt中实现定时器有两种方法. 一种是使用QObject类定时器;一种是使用QTimer类定时器.(定时器的精度依赖于操作系统和硬件,大多数平台支持20ms) 1,QObject类定时器. 通过QObj ...
- POJ2455 Secret Milking Machine
Secret Milking Machine Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 12324 Accepted ...
- http响应头里没有或者有content-length的几种可能性
对于http的请求返回结果要进行内容的长度校验主要有两种方式,二者互斥使用 1.客户端在http头(head)加Connection:keep-alive时,服务器的response是Transfer ...
- WPF PrismDialog PopupWindowAction使用MetroWindow
本示例必须在prism5.0版本以上 PopupWindowAction如何使用MetroWindow? public class Window1ViewModel:BindableBase,II ...
- 下单快发货慢:一个 JOIN SQL 引起 SqlClient 读取数据慢的奇特问题
最近遇到一个非常奇特的问题,在一个 ASP.NET Core 项目中从 SQL Server 2008 R2 中查询获取 100 条记录竟然耗时 10 多秒,如果是查询本身慢,那到不是什么奇特的问题. ...
- 三种方法在当前目录下打开cmd命令窗口
概述 运行npm的时候,每次都要cd到目录,很麻烦,所以总结了三种在当前目录下直接打开cmd窗口的方法,供以后开发时参考,相信对其他人也有用. 方法一 在当前目录按住shift再右键. 会看到右键菜单 ...
- TCP/UDP OSI_layer 4
这篇文章主要复习OSI模型中的第4层:传输层,主要包含两个协议TCP .UDP. Transport 传输层 多路复用: 一个协议为多个上层协议或者多个上层应用提供一个统一的服务 TCP/UDP 通过 ...