为什么BufferedReader的性能比BufferedInputStream差很多?

21

我明白使用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的性能,或者有其他替代机制?


5
我认为最有可能的解释是你的基准测试存在缺陷,例如你没有充分考虑JVM预热效应。请发布完整内容。 - Stephen C
@StephenC 或者可能是磁盘缓存? - John Dvorak
4
你正在比较不相同的东西——第二个测试涉及将字节转换为“char”,而第一个测试则没有。如果你需要“char”数据,使用“Reader”;如果你需要字节,请使用“InputStream”。我认为你会发现最快的方法是使用一个“BufferedReader”,它包装了一个“InputStreamReader”,该流又包装了一个“BufferedInputStream”,最后再包装一个“FileInputStream”。此外,可以参考这篇帖子来了解如何编写基准测试。 - Ted Hopp
2
没有看到你的实际代码,我无法给你一个完整的解释。但是我认为这主要有两个原因:1)你报告的时间对我来说似乎不可信,2)你没有回应JVM预热理论...这表明你不理解它的重要性。只需发布代码...以便我们可以看到你实际在做什么,并尝试复制它。 - Stephen C
嗯,我想我需要重新审视我的测试。 - Ted Hopp
显示剩余9条评论
2个回答

15

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

注意:必须与系统调用混合执行此操作可能会减慢两个操作的速度(因为系统调用可能会干扰缓存)。


3
在 BufferedReader 实现中,有一个固定的常量 defaultExpectedLineLength = 80,在 readLine 方法中用于分配 StringBuffer。如果您有大量行长超过80的大文件,则可以改进此片段。
if (s == null) 
    s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接