Почему использование BufferedInputStream для чтения файла байта за байтом быстрее, чем с помощью FileInputStream?
Я пытался прочитать файл в массив с помощью FileInputStream, и файл ~800 КБ занял около 3 секунд для чтения в память. Затем я попробовал тот же код, за исключением того, что FileInputStream заключен в BufferedInputStream, и это заняло около 76 миллисекунд. Почему чтение файла побайтово выполняется намного быстрее с BufferedInputStream, хотя я все еще читаю его побайтно? Вот код (остальная часть кода совершенно не имеет значения). Обратите внимание, что это "быстрый" код. Вы можете просто удалить BufferedInputStream, если вы хотите "медленный" код:
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(file));
int[] fileArr = new int[(int) file.length()];
for (int i = 0, temp = 0; (temp = is.read()) != -1; i++) {
fileArr[i] = temp;
}
BufferedInputStream более чем в 30 раз быстрее. Гораздо больше, чем это. Итак, почему это так, и возможно ли сделать этот код более эффективным (без использования каких-либо внешних библиотек)?
3 ответа
В FileInputStream
, метод read()
читает один байт. Из исходного кода:
/**
* Reads a byte of data from this input stream. This method blocks
* if no input is yet available.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* file is reached.
* @exception IOException if an I/O error occurs.
*/
public native int read() throws IOException;
Это собственный вызов ОС, который использует диск для чтения одного байта. Это тяжелая операция.
С BufferedInputStream
, метод делегирует перегруженному read()
метод, который читает 8192
количество байтов и буферизует их, пока они не понадобятся. Он по-прежнему возвращает только один байт (но сохраняет остальные в резерве). Таким образом, BufferedInputStream
делает меньше собственных вызовов ОС для чтения из файла.
Например, ваш файл 32768
длинные байты. Чтобы получить все байты в памяти с FileInputStream
, вам потребуется 32768
родные звонки в ОС. С BufferedInputStream
, вам потребуется только 4
независимо от количества read()
звонки ты будешь делать (еще 32768
).
Что касается того, как сделать это быстрее, вы можете рассмотреть NIO в Java 7 FileChannel
класс, но у меня нет никаких доказательств в поддержку этого.
BufferedInputStream, обернутый вокруг FileInputStream, будет запрашивать данные из FileInputStream большими кусками (по-моему, 512 байт или около того). Таким образом, если вы читаете 1000 символов по одному, FileInputStream придется только дважды перейти на диск, Это будет намного быстрее!
Это из-за стоимости доступа к диску. Предположим, у вас будет файл размером 8 КБ. Для чтения этого файла без BufferedInputStream потребуется диск доступа 8*1024 раза.
На этом этапе BufferedStream выходит на сцену и действует как посредник между FileInputStream и файлом, который нужно прочитать.
В одном кадре получит куски байтов по умолчанию 8 КБ в память, а затем FileInputStream будет читать байты от этого посредника. Это уменьшит время операции.
private void exercise1WithBufferedStream() {
long start= System.currentTimeMillis();
try (FileInputStream myFile = new FileInputStream("anyFile.txt")) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(myFile);
boolean eof = false;
while (!eof) {
int inByteValue = bufferedInputStream.read();
if (inByteValue == -1) eof = true;
}
} catch (IOException e) {
System.out.println("Could not read the stream...");
e.printStackTrace();
}
System.out.println("time passed with buffered:" + (System.currentTimeMillis()-start));
}
private void exercise1() {
long start= System.currentTimeMillis();
try (FileInputStream myFile = new FileInputStream("anyFile.txt")) {
boolean eof = false;
while (!eof) {
int inByteValue = myFile.read();
if (inByteValue == -1) eof = true;
}
} catch (IOException e) {
System.out.println("Could not read the stream...");
e.printStackTrace();
}
System.out.println("time passed without buffered:" + (System.currentTimeMillis()-start));
}