Java中的字节流文件读取教程(二)

 更新时间:2018年07月03日 09:55:39   作者:Single_Yam  
这篇文章主要给大家介绍了关于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)

大家也可以选择通过本地下载

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • idea开启热部署Devtools的步骤详解

    idea开启热部署Devtools的步骤详解

    当我们在 idea 中修改代码的时候,idea 并不会自动的重启去响应我们修改的内容,而是需要我们手动的重新启动项目才可以生效,这个是非常不方便,但是可以在 idea 中开启这个自动热部署的功能,本文给大家介绍了idea开启热部署Devtools的步骤,需要的朋友可以参考下
    2024-03-03
  • spring如何使用命名空间p简化bean的配置

    spring如何使用命名空间p简化bean的配置

    这篇文章主要介绍了spring如何使用命名空间p简化bean的配置,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Java 并行数据处理和性能分析

    Java 并行数据处理和性能分析

    这篇文章主要介绍了Java 并行数据处理和性能分析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 理解Java当中的回调机制(翻译)

    理解Java当中的回调机制(翻译)

    今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多。我要讲讲回调(callbacks)。你知道什么时候用,怎么用这个吗?你真的理解了它在java环境中的用法了吗?当我也问我自己这些问题,这也是我开始研究这些的原因
    2014-10-10
  • Mybatis集成到Spring容器的详细步骤

    Mybatis集成到Spring容器的详细步骤

    在现在的JavaEE开发过程中,我们经常会使用到Spring+SpringMVC+Mybatis这个组合,那么Mybatis是如何集成到Spring中的呢,下面通过实例代码给大家详细讲解,感兴趣的朋友跟随小编一起看看吧
    2024-03-03
  • Java中的 FilterInputStream简介_动力节点Java学院整理

    Java中的 FilterInputStream简介_动力节点Java学院整理

    FilterInputStream 的作用是用来“封装其它的输入流,并为它们提供额外的功能”。接下来通过本文给大家分享Java中的 FilterInputStream简介,感兴趣的朋友一起学习吧
    2017-05-05
  • JVM,JRE和JDK的区别小结

    JVM,JRE和JDK的区别小结

    在Java环境配置和项目启动中,JVM,JRE和JDK这三者的配置是项目启动的基础保证,本文就详细的介绍一下这三者的区别,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • spring boot executable jar/war 原理解析

    spring boot executable jar/war 原理解析

    spring boot里其实不仅可以直接以 java -jar demo.jar的方式启动,还可以把jar/war变为一个可以执行的脚本来启动,比如./demo.jar,这篇文章主要介绍了spring boot executable jar/war 原理,需要的朋友可以参考下
    2023-02-02
  • IDEA插件开发之环境搭建过程图文详解

    IDEA插件开发之环境搭建过程图文详解

    这篇文章主要介绍了IDEA插件开发之环境搭建过程,本文通过图文并茂实例代码相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • Java多线程与线程池技术分享

    Java多线程与线程池技术分享

    这篇文章主要介绍了Java多线程与线程池技术分享,线程池本质是池化技术的应用,和连接池类似,创建连接与关闭连接属于耗时操作,下文相关介绍需要的小伙伴可以参考一下
    2022-03-03

最新评论