BufferedInputStream es una herramienta muy útil cuando queremos leer datos de un archivo o cualquier otro InputStream de forma más eficiente. Su funcionamiento se basa en envolver otro InputStream, como un FileInputStream, y almacenar internamente un buffer con varios bytes leídos de una sola vez. Esto evita que cada llamada a read tenga que acceder directamente al disco o al medio subyacente, lo que puede ser costoso en términos de rendimiento.
Para entenderlo mejor, imaginemos que abrimos un archivo y creamos un BufferedInputStream que lo envuelve. Cuando llamamos a read sobre este BufferedInputStream, aunque solo nos devuelva un byte, internamente ha leído muchos más bytes y los ha guardado en un array interno. Así, las siguientes llamadas a read simplemente van devolviendo bytes desde ese buffer, sin necesidad de ir al disco cada vez. Esto lo podemos comprobar fácilmente con un bucle que lee byte a byte hasta que read devuelve -1, indicando el final del archivo.
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("archivo.dat"))) {
int octeto;
do {
octeto = bis.read();
if (octeto != -1) {
System.out.print(octeto + " ");
}
} while (octeto != -1);
}
Si queremos hacer la salida más legible, podemos imprimir un salto de línea cada cierto número de bytes leídos, por ejemplo cada 20, para no tener una línea interminable.
Una característica muy interesante de BufferedInputStream es que soporta los métodos mark y reset, que nos permiten establecer una marca en la posición actual del stream y luego volver a esa posición más adelante. Esto es especialmente útil cuando necesitamos rebobinar y releer una parte del stream sin tener que volver a abrirlo o recargarlo desde cero.
El método mark recibe un parámetro llamado readLimit, que indica cuántos bytes queremos que el stream recuerde a partir de la posición actual. Si le decimos que recuerde 100 bytes, por ejemplo, el BufferedInputStream almacenará esos bytes para que podamos volver a ellos con reset.
bis.mark(100);
Después de establecer la marca, podemos leer normalmente. Si en algún momento queremos volver a la posición marcada, simplemente llamamos a reset y el stream volverá a esa posición, permitiéndonos releer esos bytes.
bis.reset();
Esto puede generar comportamientos curiosos. Por ejemplo, si leemos bytes y cuando llegamos al byte número 50 llamamos a reset, volveremos a la posición marcada al principio, y si seguimos leyendo y reseteando en el 50, podemos crear un bucle infinito que repite la lectura de los primeros 50 bytes una y otra vez.
Para evitar esto, podemos controlar cuántas veces permitimos volver atrás, usando un contador que limite las llamadas a reset.
int retornos = 0;
int octeto;
bis.mark(100);
do {
octeto = bis.read();
if (octeto == 50 && retornos < 4) {
bis.reset();
retornos++;
} else if (octeto != -1) {
System.out.print(octeto + " ");
}
} while (octeto != -1);
Es importante tener en cuenta que si llamamos a reset sin haber llamado antes a mark, BufferedInputStream lanzará una excepción, porque no tiene una posición marcada a la cual volver. Por eso siempre debemos asegurarnos de llamar a mark antes de usar reset.
Además, el valor que pasamos a mark como readLimit no es arbitrario. Si intentamos leer más bytes que ese límite antes de llamar a reset, el stream puede perder la información almacenada y el comportamiento de reset puede ser impredecible o fallar. Por eso es recomendable ajustar ese valor según nuestras necesidades y el tamaño del buffer interno.
En resumen, BufferedInputStream no solo mejora la eficiencia de lectura al reducir accesos al medio subyacente, sino que también nos ofrece la posibilidad de marcar posiciones y volver atrás en el stream, algo que no es posible con un InputStream normal. Aunque no usemos mark y reset con frecuencia, conocer su funcionamiento puede ser muy útil para ciertos casos donde necesitamos releer fragmentos de datos sin recargar todo desde cero.