¿Por qué usar BufferedInputStream para leer un byte de archivo por byte más rápido que usando FileInputStream?

Estaba tratando de leer un archivo en una matriz mediante el uso de FileInputStream, y un archivo ~ 800 KB tardó unos 3 segundos en leer en la memoria. Luego intenté con el mismo código, excepto con FileInputStream en un BufferedInputStream que tardó unos 76 milisegundos. ¿Por qué leer un byte de archivo por byte es mucho más rápido con un BufferedInputStream a pesar de que todavía lo estoy leyendo byte a byte? Aquí está el código (el rest del código es completamente irrelevante). Tenga en cuenta que este es el código “rápido”. Puedes eliminar el BufferedInputStream si quieres el código “lento”:

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 es más de 30 veces más rápido. Mucho más que eso. Entonces, ¿por qué es esto y es posible hacer que este código sea más eficiente (sin usar ninguna biblioteca externa)?

En FileInputStream , el método read() lee un solo byte. Desde el código fuente:

 /** * 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 -1 if the end of the * file is reached. * @exception IOException if an I/O error occurs. */ public native int read() throws IOException; 

Esta es una llamada nativa al sistema operativo que usa el disco para leer el byte único. Esta es una operación pesada.

Con un BufferedInputStream , el método delega en un método de read() sobrecargado que lee 8192 cantidad de bytes y los almacena en búfer hasta que se necesiten. Todavía devuelve solo el byte único (pero mantiene a los demás en reserva). De esta forma, BufferedInputStream realiza menos llamadas nativas al sistema operativo para leer desde el archivo.

Por ejemplo, su archivo tiene 32768 bytes de longitud. Para obtener todos los bytes en la memoria con un FileInputStream , necesitará 32768 llamadas nativas al sistema operativo. Con un BufferedInputStream , solo necesitará 4 , independientemente de la cantidad de llamadas de read() que realice (aún 32768 ).

En cuanto a cómo hacerlo más rápido, es posible que desee considerar la clase NIO FileChannel Java 7, pero no tengo pruebas para respaldar esto.

Un BufferedInputStream envuelto alrededor de un FileInputStream, solicitará datos de FileInputStream en grandes fragmentos (creo que 512 bytes más o menos). Por lo tanto, si lee 1000 caracteres de uno en uno, FileInputStream solo tendrá que ir al disco dos veces . ¡Esto será mucho más rápido!

Es por el costo del acceso al disco. Supongamos que tendrá un archivo de 8kb. Se necesitarán 8 * 1024 veces acceso al disco para leer este archivo sin BufferedInputStream.

En este punto, BufferedStream llega a la escena y actúa como intermediario entre FileInputStream y el archivo que se va a leer.

De una sola vez, obtendrá trozos de bytes. El valor predeterminado es 8kb en la memoria y luego FileInputStream leerá los bytes de este intermediario. Esto disminuirá el tiempo de la operación.

 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)); }