Подтвердить что ты не робот

Почему производительность BufferedReader намного хуже, чем BufferedInputStream?

Я понимаю, что использование BufferedReader (wrapping FileReader) будет значительно медленнее, чем использование BufferedInputStream (wrapping 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 или альтернативный механизм?

4b9b3361

Ответ 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, которая используется в методе readLine при распределении StringBuffer. Если у вас большой файл с большим количеством строк длиннее 80, этот фрагмент может быть чем-то, что можно улучшить

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