【问题标题】:Why is the performance of BufferedReader so much worse than BufferedInputStream?为什么BufferedReader的性能比BufferedInputStream差那么多?
【发布时间】:2023-03-07 04:03:01
【问题描述】:

我知道使用 BufferedReader(包装 FileReader)比使用 BufferedInputStream(包装 FileInputStream)要慢得多,因为必须将原始字节转换为字符。但我不明白为什么它这么慢!这是我正在使用的两个代码示例:

BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(filename));
try {
  byte[] byteBuffer = new byte[bufferSize];
  int numberOfBytes;
  do {
    numberOfBytes = inputStream.read(byteBuffer, 0, bufferSize);
  } while (numberOfBytes >= 0);
}
finally {
  inputStream.close();
}

和:

BufferedReader reader = new BufferedReader(new FileReader(filename), bufferSize);
try {
  char[] charBuffer = new char[bufferSize];
  int numberOfChars;
  do {
    numberOfChars = reader.read(charBuffer, 0, bufferSize);
  } while (numberOfChars >= 0);
}
finally {
  reader.close();
}

我尝试过使用各种缓冲区大小的测试,所有这些测试都包含一个 150 兆字节的文件。以下是结果(缓冲区大小以字节为单位;时间以毫秒为单位):

Buffer   Input
  Size  Stream  Reader
 4,096    145     497
 8,192    125     465
16,384     95     515
32,768     74     506
65,536     64     531

可以看出,BufferedInputStream 的最快时间(64 毫秒)比 BufferedReader 的最快时间(465 毫秒)快七倍。正如我上面所说,我没有显着差异的问题。但是这么大的差异似乎不合理。

我的问题是:是否有人对如何提高 BufferedReader 的性能或替代机制提出建议?

【问题讨论】:

  • 我认为最可能的解释是您的基准测试存在缺陷;例如您没有正确考虑 JVM 预热效果。请发布完整的内容。
  • @StephenC 或者磁盘缓存?
  • 您在比较苹果和橙子——第二个测试涉及将字节转换为char,而第一个不这样做。如果您需要char 数据,请使用Reader;如果您需要字节,请使用InputStream。我想你会发现最快的将是 BufferedReader 包裹 InputStreamReader 包裹 BufferedInputStream 包裹 FileInputStream。另请参阅this thread,了解如何编写基准测试。
  • 结果也可能取决于所使用的字符编码。
  • 没有看到你的实际代码,我无法给你完整的解释。但我认为这的主要原因是 1)您报告的时间对我来说似乎不可信,以及 2)您没有对 JVM 预热理论做出回应……这表明您不了解它的重要性。只需发布代码......这样我们就可以看到您实际在做什么,并尝试重现它。

标签: java performance bufferedreader bufferedinputstream


【解决方案1】:

BufferedReader 已将字节转换为字符。相对于数据块的直接副本,这种逐字节解析和复制到更大类型的成本很高。

byte[] bytes = new byte[150 * 1024 * 1024];
Arrays.fill(bytes, (byte) '\n');

for (int i = 0; i < 10; i++) {
    long start = System.nanoTime();
    StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes));
    long time = System.nanoTime() - start;
    System.out.printf("Time to decode %,d MB was %,d ms%n",
            bytes.length / 1024 / 1024, time / 1000000);
}

打印

Time to decode 150 MB was 226 ms
Time to decode 150 MB was 167 ms

注意:必须与系统调用混合执行此操作会减慢这两种操作(因为系统调用会干扰缓存)

【讨论】:

    【解决方案2】:

    在BufferedReader实现中有一个固定常量defaultExpectedLineLength = 80,在分配StringBuffer时在readLine方法中使用。如果你有很多行超过 80 行的大文件,这个片段可能是可以改进的东西

    if (s == null) 
        s = new StringBuffer(defaultExpectedLineLength);
    s.append(cb, startChar, i - startChar);
    

    【讨论】:

      猜你喜欢
      • 2016-04-21
      • 1970-01-01
      • 2014-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-10
      • 2016-11-12
      • 1970-01-01
      相关资源
      最近更新 更多