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

我知道使用 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 ms) 比 BufferedReader 的最快时间 (465 ms) 快 7 倍。正如我上面所说,我没有显着差异的问题;但这种差异似乎不合理。

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


答案 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实现中有一个固定常量,在分配时在方法中使用。如果你有一个大文件,有很多行比80长,这个片段可能是可以改进的。defaultExpectedLineLength = 80readLineStringBuffer

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

推荐