Cuando queremos cargar datos desde un archivo en Java, la clase FileInputStream es nuestra aliada para leer esa información almacenada en disco. Su constructor es muy parecido al de FileOutputStream, ya que acepta tanto una cadena con el nombre del archivo, como un objeto File que referencia al archivo que queremos leer. Para manejarlo de forma genérica, solemos guardar la instancia en una variable de tipo InputStream, lo que nos permite acceder a los métodos comunes de todos los streams de entrada.
Al abrir un archivo con FileInputStream, es importante tener en cuenta que puede lanzar una excepción FileNotFoundException si el archivo no existe o si no tenemos permisos para acceder a él. Por eso, siempre conviene envolver la apertura en un bloque try-catch para manejar estos casos sin que el programa falle abruptamente.
El método fundamental para leer datos es read. Cuando lo llamamos sin parámetros, lee un único byte y devuelve su valor como un entero. Aunque devuelve un int, en realidad solo nos interesa el byte menos significativo, ya que los 24 bits más altos se pueden descartar. Esta elección de devolver un int en lugar de un byte tiene una razón muy práctica: cuando llegamos al final del archivo, read devuelve -1, un valor que no puede confundirse con ningún byte válido (que siempre está entre 0 y 255). Así, podemos detectar fácilmente cuándo hemos terminado de leer.
Para leer byte a byte, podemos usar un bucle do-while que llame a read repetidamente. Mientras el valor leído sea distinto de -1, seguimos procesando los datos. Por ejemplo, podemos imprimir cada byte leído. Cuando read devuelve -1, sabemos que hemos llegado al final y podemos salir del bucle. Este método es sencillo pero poco eficiente, ya que procesa un byte a la vez.
Para mejorar la eficiencia, FileInputStream ofrece sobrecargas de read que aceptan un array de bytes. Si le pasamos un array, intentará llenarlo con tantos bytes como pueda leer de una sola vez, hasta el tamaño del array o hasta que no haya más datos. El método devuelve un entero que indica cuántos bytes se han leído realmente, lo que es crucial para saber cuántos elementos del array contienen datos válidos.
Además, existe una versión más avanzada de read que acepta un array de bytes, un offset y una longitud. Esto permite leer datos directamente en una parte específica del array, empezando en la posición offset y leyendo hasta length bytes. Así podemos controlar con precisión dónde se almacenan los datos leídos y cuántos bytes queremos procesar en cada llamada.
Para entender mejor cómo funciona, imaginemos que tenemos un array de 4 bytes y queremos leer datos en bloques. Si el archivo tiene 10 bytes, la primera llamada a read llenará las primeras 4 posiciones del array y devolverá 4. La siguiente llamada podrá leer los siguientes bytes y almacenarlos en otra parte del array, por ejemplo, empezando en la posición 200 si usamos el offset. Esto nos permite manejar la lectura en fragmentos y distribuir los datos en el array según nuestras necesidades.
Es importante recordar que si el array es más pequeño que la cantidad total de datos, tendremos que hacer varias llamadas a read para leer todo el contenido. Cada llamada nos indicará cuántos bytes ha leído, y así podemos ir procesando el archivo por partes.
Finalmente, cuando terminamos de leer, debemos cerrar el stream con el método close. Esto libera los recursos asociados y permite que otros procesos puedan acceder al archivo si estaba bloqueado. Aunque existen otros métodos como available, mark y reset, que pueden ser útiles en casos más avanzados, para empezar con la lectura básica de archivos en Java con FileInputStream lo esencial es entender cómo usar read y close correctamente.
Un ejemplo básico para leer un archivo byte a byte podría ser:
try (InputStream fis = new FileInputStream("datos.txt")) {
int val;
do {
val = fis.read();
if (val != -1) {
System.out.println(val);
} else {
System.out.println("fin");
}
} while (val != -1);
} catch (IOException e) {
e.printStackTrace();
}
Y para leer en bloques usando un array de bytes:
try (InputStream fis = new FileInputStream("datos.txt")) {
byte[] arr = new byte[4];
int cuantos = fis.read(arr);
System.out.println("Bytes leídos: " + cuantos);
for (int i = 0; i < cuantos; i++) {
System.out.println(arr[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
Así podemos manejar la lectura de archivos de forma eficiente y controlada, adaptándonos a las necesidades de nuestro programa.