当前位置: 首页 > 编程笔记 >

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

陆正德
2023-03-14
本文向大家介绍Java中的字节流文件读取教程(二),包括了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 流就是用于读写文件内容的一种设计,它能完成将磁盘文件内容输出到内存或者是将内存数据输出到磁盘文件的数据传输

  • 问题内容: 请看下面的代码 在这里,首先获取文件的字节,然后将其写入文本文件。然后,我阅读了该文本文件,逐行阅读,并为每一行生成了一个单独的.txt文件。现在,原始程序被拆分为数千个文件。现在,我需要阅读所有.txt文件并重新生成.txt文件。我不知道怎么做最后一件事。我怎样才能做到这一点?请帮忙! 问题答案: 如果要操作任何类型的文件,请不要认为它们包含文本数据,而应将它们视为包含字节的二进制文

  • 我想以UTF-8快速地逐行读取大的csv文件(大约~1GB)。我已经为它创建了一个类,但它不能正常工作。UTF-8从2字节解码西里尔符号。我使用字节缓冲区来读取它,例如,它有10个字节的长度。因此,如果文件中的符号由10和11字节组成,它将无法正常解码:(

  • 问题内容: 我正在尝试使用Java读取CSV文件。有些文件的开头可能有字节顺序标记,但不是全部。如果存在,字节顺序将与第一行的其余部分一起读取,从而导致字符串比较出现问题。 是否存在一种跳过字节顺序标记的简单方法? 问题答案: 你正在以这种方式使用它:

  • 我正在尝试制作一个十六进制转储应用程序,为此,我需要读取文件字节。我正在使用ApacheIO版本2.8.0来进行十六进制转储。这是我正在使用的代码: 我得到的错误是:我查了一下,没有任何建议尝试如添加和到清单工作。任何帮助都很感激

  • 问题内容: 在我的一生中,我一直无法找到与我想做的事情相匹配的问题,因此在这里我将解释我的用例。如果您知道某个主题已涵盖此问题的答案,请随时将我引向该主题。:) 我有一段代码可以定期(每20秒)将文件上传到Amazon S3。该文件是由另一个进程写入的日志文件,因此此功能实际上是尾随日志的一种方式,以便某人可以半实时读取其内容,而不必直接访问日志所在的计算机。 。 直到最近,我只是一直使用S3 P