Фундаментальное заблуждение состоит в том, чтобы предполагать, что файл читается побайтно. Большинство запоминающих устройств, включая жесткие диски и диски с solid состояниями, организуют данные в блоки . Точно так же сетевые протоколы передают данные пакетами , а не отдельными байтами.
Это влияет на работу аппаратного обеспечения контроллера и программного обеспечения нижнего уровня (драйверов и операционной системы). Часто на этом уровне невозможно даже передать один байт. Таким образом, запрос чтения одного байта заканчивается чтением одного блока и игнорированием всего, кроме одного байта. Хуже того, запись одного байта может означать чтение всего блока, изменение одного байта и запись блока обратно в устройство. Для передачи по сети отправка пакета с полезной нагрузкой всего в один байт подразумевает использование 99% полосы пропускания для метаданных, а не фактической полезной нагрузки.
Обратите внимание, что иногда требуется немедленный ответ или требуется запись определенно завершено в какой-то момент, например, для безопасности. Вот почему вообще существует небуферизованный ввод-вывод. Но для большинства обычных случаев использования вы все равно хотите передавать последовательность байтов, и ее следует передавать фрагментами размера, подходящего для базового оборудования.
Обратите внимание, что даже если базовая система вводит буферизацию в свои или когда оборудование действительно передает отдельные байты, выполнение 100 вызовов операционной системы для передачи одного байта на каждом по-прежнему значительно медленнее, чем выполнение одного вызова операционной системы, сообщающего ему передать 100 байтов за один раз.
Но вы не должны рассматривать буфер как нечто среднее между файлом и вашей программой, как показано на вашем рисунке. Вы должны рассматривать буфер как часть вашей программы. Точно так же, как вы не рассматриваете объект String
как нечто среднее между вашей программой и источником символов, а скорее как естественный способ обработки таких элементов. Например, когда вы используете метод массового чтения InputStream
(например, FileInputStream
) с достаточно большим целевым массивом, нет необходимости оборачивать входной поток в BufferedInputStream
; это не улучшит производительность. Вам следует просто держаться подальше от однобайтового метода чтения , насколько это возможно.
В качестве другого практического примера, когда вы используете InputStreamReader
, он уже будет считывать байты в буфер (поэтому дополнительных BufferedInputStream
не требуется), а внутренний CharsetDecoder
будет работать с этим буфером, записывая полученные символы в целевой буфер символов. Когда вы используете, например, Scanner
, операции сопоставления с образцом будут работать с этим целевым буфером символов операции декодирования кодировки (когда источником является InputStream
или ByteChannel
). Затем при доставке результатов совпадения в виде строк они будут созданы другой операцией массового копирования из буфера символов. Так что обработка данных по частям уже является нормой, а не исключением.
Это было включено в дизайн NIO. Таким образом, вместо поддержки однобайтового метода чтения и его исправления путем предоставления декоратора буферизации, как это делает InputStream
API, подтипы ByteChannel
NIO предлагают только методы, использующие буферы, управляемые приложением .
Таким образом, можно сказать, что буферизация не улучшает производительность, это естественный способ передачи и обработки данных. Скорее, отказ от буферизации снижает производительность, требуя преобразования из обычных операций с массивом данных в операции с отдельным элементом.