数组中的 reading/writing 是否比 reading/writing 一个 char/byte 一个一个地高效?

Is reading/writing in an array more efficient than reading/writing a char/byte one by one?

try(FileReader reader = new FileReader("input.txt")) {

    int c;
    while ((c = reader.read()) != -1)
        System.out.print((char)c);

} catch (Exception ignored) { }

在这段代码中,我逐个读取一个字符。以某种方式一次将 a 读入一个字符数组是否更有效?换句话说,读取数组时是否会发生任何类型的优化?

例如,在这段代码中,我有一个名为 arrchar 数组,我读入它直到没有剩余可读内容。是不是更有效率?

    try(FileReader reader = new FileReader("input.txt")) {

        int size;
        char[] arr = new char[100];
        while ((size = reader.read(arr)) != -1)
            for (int i = 0; i < size; i++)
                System.out.print(arr[i]);

    } catch (Exception ignored) { }

该问题适用于 reading/writing 和 chars/bytes。

取决于 reader。不过,答案可能是肯定的。 Reader 或 InputStream 是实际的 'raw' 驱动程序(不只是包装另一个 reader 或输入流的驱动程序,而是实际与 OS 对话的驱动程序获取数据) - 它可以通过要求 OS 读取单个字符来很好地实现单字符 read() 方法。

最后,你有一个磁盘,磁盘 return 块中的数据。所以如果你要求 1 个字节,你有 2 个选项作为计算机:

  1. 向磁盘询问包含要读取的字节的块。将块存储在内存中的某个地方一段时间。 Return一个字节;在接下来的几分钟里,如果更多的字节请求来自同一个块,return 来自内存中存储的数据,根本不用问磁盘。注意:这需要内存!谁来分配?内存多大合适?棘手的问题。 OSes 倾向于提供低级工具,不喜欢为这些问题中的任何一个选择值。

  2. 向磁盘询问包含要读取的字节的块。从此块中找到所需的 1 个字节。忽略其余数据,return 只是那一个字节。如果过一会儿从该块中请求另一个字节...再次向磁盘请求整个块,然后重复此例程。

你得到这两种模型中的哪一种取决于很多因素:例如:它是什么类型的磁盘,你有什么OS,底层java reader是什么你在用。但是你最终进入第二种模式是合理的,也就是说,正如你可能知道的那样,通常非常慢,因为你最终读取同一个块 4000 多次而不是一次。

那么,如何解决这个问题?好吧,java 也不知道 OS 在做什么,所以最安全的做法是让 java 进行缓存。那么你就不会依赖 OS 正在做的任何事情。

你可以自己写,所以不是:

for (int i = in.read(); i != -1; i = in.read()) {
    processOneChar((char) i);
}

你可以这样做:

char[] buffer = new char[4096];
while (true) {
    int r = in.read(buffer);
    if (r == -1) break;
    for (int i = 0; i < r; i++) processOneChar(buffer[i]);
}

更多代码,但现在第二种情况(同一个块从磁盘上读取了很多次)不再发生;你给了 OS 自由 return 给你最多 4096 个字符的数据。

或者,使用 java 内置函数:BufferedX:

BufferedReader br = new BufferedReader(in);
for (int i = br.read(); i != -1; i = br.read()) {
    processOneChar((char) i);
}

BufferedReader 的实现保证 java 会负责制作一些大小合理的缓冲区,以避免重新读取磁盘上的同一块。

注意:请注意,不应使用您正在使用的 FileReader 构造函数。它使用平台默认编码(任何时候将字节转换为字符,都会涉及编码),平台默认是导致无法测试的错误的良方,这是非常糟糕的。使用 new FileReader(file, StandardCharsets.UTF_8) 代替,或者更好的是,使用新的 API:

Path p = Paths.get("C:/file.txt");
try (BufferedReader br = Files.newBufferedReader(p)) {
    for (int i = br.read(); i != -1; i = br.read()) {
        processOneChar((char) i);
    }
}

注意这个:

  1. 默认为 UTF-8,因为文件 API 默认为 UTF-8,这与 VM 中的大多数地方不同。
  2. 立即制作缓冲reader,无需自己制作。
  3. 通过使用 ARM 块正确管理资源(无论此代码如何退出,无论是正常还是异常,都确保资源关闭)。
  4. 因为涉及 BufferedX,所以没有 'read the same block a lot' 性能漏洞的风险。

注意:写的时候也是一样的逻辑; SSD 等磁盘一次只能写入整个块。现在它不仅写起来像糖蜜一样慢,而且还会毁掉你的磁盘,因为它们的写入次数有限。