InputStream、InputStreamReader 和 BufferedReader 如何在 Java 中协同工作?

我正在学习Android开发(我是一般的编程初学者)并学习HTTP网络,并在本课中看到以下代码:

private String readFromStream(InputStream inputStream) throws IOException {
  StringBuilder output = new StringBuilder();
  if (inputStream != null) {
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
    BufferedReader reader = new BufferedReader(inputStreamReader);
    String line = reader.readLine();
    while (line != null) {
      output.append(line);
      line = reader.readLine();
    }
  }
  return output.toString();
}

我不明白 InputStream、InputStreamReader 和 BufferedReader 是做什么的。它们都有一个read()方法,在BufferedReader的情况下还有readLine()。为什么我不能只使用InputStream或只添加InputStreamReader?为什么我需要添加缓冲读取器?我知道这与效率有关,但我不明白如何。

我一直在研究,BufferedReader的文档试图解释这一点,但我仍然不知道谁在做什么:

通常,对 Reader 发出的每个读取请求都会导致对基础字符或字节流发出相应的读取请求。因此,建议将 BufferedReader 包装在任何读取() 操作可能代价高昂的读取器上,例如 FileReaders 和 InputStreamReaders。例如

BufferedReader in = new BufferedReader(new FileReader("foo.in"));将缓冲指定文件中的输入。如果没有缓冲,每次调用 read() 或 readLine() 都可能导致从文件中读取字节,转换为字符,然后返回,这可能非常低效。

所以,我明白 InputStream 只能读取一个字节,InputStreamReader 只能读取单个字符,BufferedReader 只能读取一整行,并且它还可以提高效率,而这正是我所没有的。我想更好地了解谁在做什么,以便了解为什么我需要他们三个人,如果没有他们中的任何一个,会有什么区别。

我在这里和网络上的其他地方研究了很多,似乎没有找到任何我能理解的解释,几乎所有的教程都只是重复文档信息。以下是一些相关的问题,也许可以开始解释这一点,但不要深入解决我的困惑:Q1Q2Q3Q4。我认为这可能与最后一个问题对系统调用和返回的解释有关。但我想了解这一切是什么意思。

难道 BufferedReader 的 readLine() 调用 InputStreamReader 的 read() 方法,而后者又调用 InputStream 的 read() 方法吗?InputStream返回转换为int的字节,一次返回一个字节,InputStreamReader读取足够的这些字节以形成单个字符并将其转换为int并一次返回一个字符,而BufferedReader读取足够多的这些字符表示为整数以组成整行?并将整行作为字符串返回,只返回一次而不是多次?我不知道,我只是想了解事情是如何运作的。

提前非常感谢!


答案 1

这个流在Java概念和用法链接,给出了一个非常好的解释。

This

Streams,Reader,Writers,BufferedReader,BufferedWriter - 这些是您将在Java中处理的术语。Java中提供了一些类来操作输入和输出。真正值得知道这些是如何相关的以及如何使用它。这篇文章将详细探讨Java和其他相关类中的Streams。因此,让我们开始:

让我们从高层次上定义其中的每一个,然后深入挖掘。

用于处理字节级数据的

读取器/编写器
用于处理字符级别。它还支持各种字符编码。

BufferedReader/BufferedWriter
提高性能。要读取的数据将被缓冲到内存中,以便快速访问。

虽然这些是用于获取输入的,但输出也存在相应的类。例如,如果有一个用于读取字节流的 InputStream,而 OutputStream 将帮助写入字节流。

InputStreams
Java提供的IntegratedStreams有很多种类型。每个都连接到不同的数据源,如字节数组,文件等。

例如,FileInputStream 连接到文件数据源,并可用于从文件中读取字节。而ByteArrayInputStream可用于将字节数组视为输入流。

OutputStream
这有助于将字节写入数据源。对于几乎每个输入流,只要有意义,就会有相应的输出流。


更新

什么是缓冲流?

在这里,我引用了Buffered Streams,Java文档(带有技术解释):

缓冲流

到目前为止,我们看到的大多数示例都使用无缓冲 I/O。这意味着每个读取或写入请求都由底层操作系统直接处理。这可能会使程序的效率大大降低,因为每个这样的请求通常会触发磁盘访问,网络活动或其他一些相对昂贵的操作。

为了减少这种开销,Java 平台实现了缓冲 I/O 流。缓冲输入流从称为缓冲区的内存区域读取数据;仅当缓冲区为空时,才会调用本机输入 API。同样,缓冲输出流将数据写入缓冲区,并且仅当缓冲区已满时才调用本机输出 API。

有时我在阅读技术文档时会掉头。所以,在这里我引用 https://yfain.github.io/Java4Kids/ 更人道的解释

通常,磁盘访问比在内存中执行的处理慢得多。这就是为什么访问磁盘一千次以读取1,000字节的文件不是一个好主意。为了最大限度地减少访问磁盘的次数,Java 提供了缓冲区,这些缓冲区充当数据的存储库。

enter image description here

在读取 FileInputStream 和 BufferedInputStream 时,类 BufferedInputStream 充当 FileInputStream 和文件本身之间的中间人。它一次性将一大块字节从文件读取到内存(缓冲区)中,然后FileInputStream对象从那里读取单个字节,这是快速的内存到内存操作。BufferedOutputStream 与类 FileOutputStream 的工作方式类似。

这里的主要思想是尽量减少磁盘访问。缓冲流不会改变原始流的类型,它们只会使读取更加高效。程序执行流链接(或流管道)以连接流,就像管道在管道中连接一样。


答案 2
  • InputStream, OutputStream, byte[], ByteBuffer用于二进制数据。
  • Reader, Writer, String, char用于文本,内部Unicode,以便世界上所有脚本都可以组合(例如希腊语和阿拉伯语)。

  • InputStreamReader并在两者之间架起一座桥梁。如果你有一些 InputStream,并且知道它的字节实际上是某些编码中的文本,Charset,那么你可以包装 InputStream:OutputStreamWriter

    try (InputStreamReader reader =
            new InputStreamReader(stream, StandardCharsets.UTF_8)) {
         ... read text ...
    }
    

有一个没有字符集的构造函数,但这不是可移植的,因为它使用默认的平台编码。

在 Android StandardCharset 上,使用“UTF-8”

派生类,并向父级添加一些东西。FileInputStreamBufferedReaderInputStreamReader

FileInputStream 用于从 File 输入,而 BufferedReader 使用内存缓冲区,因此实际的物理读取不会不读取字符(效率低下)。一起,您可以向原始阅读器添加缓冲。new BufferedReader(otherReader)

所有这一切都明白了,有一个实用程序类,其方法类似于增加了额外的简洁性。FilesnewBufferedReader(Path, Charset)


推荐