Leer un archivo línea por línea en Java

En Ciencias de la Computación, un archivo es un recurso utilizado para registrar datos de forma discreta en el dispositivo de almacenamiento de una computadora. En Java, un recurso suele ser un objeto que implementa...

En Ciencias de la Computación, un archivo es un recurso utilizado para registrar datos discretamente en el dispositivo de almacenamiento de una computadora. En Java, un recurso suele ser un objeto que implementa la interfaz AutoCloseable.

La lectura de archivos y recursos tiene muchos usos:

  • Estadísticas, análisis e informes
  • Aprendizaje automático
  • Manejo de grandes archivos de texto o registros

A veces, estos archivos pueden ser absurdamente grandes, con gigabytes o terabytes almacenados, y leerlos en su totalidad es ineficiente.

Ser capaz de leer un archivo línea por línea nos brinda la posibilidad de buscar solo la información relevante y detener la búsqueda una vez que hayamos encontrado lo que estamos buscando. También nos permite dividir los datos en partes lógicas, como si el archivo tuviera formato CSV.

Hay algunas opciones diferentes para elegir cuando necesita leer un archivo línea por línea.

Escáner

Una de las formas más sencillas de leer un archivo línea por línea en Java podría implementarse usando el Escáner clase. Un escáner divide su entrada en tokens utilizando un patrón delimitador, que en nuestro caso es el carácter de nueva línea:

1
2
3
4
5
Scanner scanner = new Scanner(new File("filename"));
while (scanner.hasNextLine()) {
   String line = scanner.nextLine();
   // process the line
}

El método hasNextLine() devuelve true si hay otra línea en la entrada de este escáner, pero el escáner en sí no avanza más allá de ninguna entrada ni lee ningún dato en este punto.

Para leer la línea y continuar, debemos usar el método nextLine(). Este método hace avanzar el escáner más allá de la línea actual y devuelve la entrada que no se alcanzó inicialmente. Este método devuelve el resto de la línea actual, excluyendo cualquier separador de línea al final de la línea. Luego, la posición de lectura se establece al comienzo de la siguiente línea, que se leerá y se devolverá al volver a llamar al método.

Dado que este método continúa buscando a través de la entrada en busca de un separador de línea, puede almacenar en búfer toda la entrada mientras busca el final de la línea si no hay separadores de línea presentes.

Lector en búfer

La clase BufferedReader representa una forma eficiente de leer los caracteres, matrices y líneas de una entrada de caracteres. corriente.

Como se describe en la denominación, esta clase utiliza un búfer. La cantidad predeterminada de datos que se almacenan en el búfer es de 8192 bytes, pero podría establecerse en un tamaño personalizado por motivos de rendimiento:

1
BufferedReader br = new BufferedReader(new FileReader(file), bufferSize);

El archivo, o más bien una instancia de una clase File, no es una fuente de datos adecuada para el BufferedReader, por lo que necesitamos usar un FileReader, que amplía InputStreamReader. Es una clase conveniente para leer información de archivos de texto y no es necesariamente adecuada para leer un flujo de bytes sin formato:

1
2
3
4
5
6
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
    String line;
    while ((line = br.readLine()) != null) {
       // process the line
    }
}

La inicialización de un lector almacenado en búfer se escribió utilizando la sintaxis pruebe con recursos, específica de Java 7 o superior. Si está utilizando una versión anterior, debe inicializar la variable br antes de la instrucción try y cerrarla en el bloque finally.

Aquí hay un ejemplo del código anterior sin la sintaxis de prueba con recursos:

1
2
3
4
5
6
7
8
9
BufferedReader br = new BufferedReader(new FileReader(file));
try {
    String line;
    while ((line = br.readLine()) != null) {
       // process the line
    }
} finally {
    br.close();
}

El código recorrerá las líneas del archivo proporcionado y se detendrá cuando se encuentre con la línea nulo, que es el final del archivo.

No se confunda ya que null no es igual a una línea vacía y el archivo será leído hasta el final.

El método de líneas

Una clase BufferedReader también tiene un método lines que devuelve un Stream. Este flujo contiene líneas que fueron leídas por BufferedReader, como sus elementos.

Puede convertir fácilmente este flujo en una lista si necesita:

1
2
3
4
5
List<String> list = new ArrayList<>();

try (BufferedReader br = new BufferedReader(new FileReader(file))) {
    list = br.lines().collect(Collectors.toList());    
}

Leer esta lista es lo mismo que leer un flujo, que se tratan en la siguiente sección:

1
list.forEach(System.out::println);

Flujos de Java 8

Si ya está familiarizado con Java 8 Corrientes, puede usarlos como alternativa más limpia al bucle heredado:

1
2
3
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
    stream.forEach(System.out::println);
}

Aquí estamos usando la sintaxis try-with-resources una vez más, inicializando un flujo de líneas con el método auxiliar estático Files.lines(). La referencia del método System.out::println se usa para fines de demostración, y debe reemplazarlo con cualquier código que usará para procesar sus líneas de texto.

Además de una API limpia, las secuencias son muy útiles cuando desea aplicar múltiples operaciones a los datos o filtrar algo.

Supongamos que tenemos una tarea para imprimir todas las líneas que se encuentran en un archivo de texto dado y terminan con el carácter "/". Las líneas deben transformarse a mayúsculas y ordenarse alfabéticamente.

Al modificar nuestro ejemplo inicial de "Streams API" obtendremos una implementación muy limpia:

1
2
3
4
5
6
7
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
    stream
        .filter(s -> s.endswith("/"))
        .sorted()
        .map(String::toUpperCase)
        .forEach(System.out::println);
}

El método filter() devuelve un flujo que consta de los elementos de este flujo que coinciden con el predicado dado. En nuestro caso vamos a dejar solo los que terminan en "/".

El método map() devuelve un flujo que consta de los resultados de aplicar la función dada a los elementos de este flujo.

El método toUpperCase() de una clase String nos ayuda a lograr el resultado deseado y se usa aquí como referencia de método, al igual que la llamada println de nuestro ejemplo anterior.

El método sorted() devuelve un flujo que consta de los elementos de este flujo, ordenados según el orden natural. También puede proporcionar un ‘Comparador’ personalizado y, en ese caso, la clasificación se realizará de acuerdo con él.

Si bien el orden de las operaciones se puede cambiar para los métodos filter(), sorted() y map(), forEach() siempre debe colocarse al final, ya que es una operación de terminal. . Devuelve void y, de hecho, no se le puede encadenar nada más.

Apache Commons

Si ya está utilizando Apache Commons en su proyecto, es posible que desee utilizar el asistente que lee todas las líneas de un archivo en una List<String>:

1
2
3
4
List<String> lines = FileUtils.readLines(file, "UTF-8");
for (String line: lines) {
    // process the line
}

Recuerde que este enfoque lee todas las líneas del archivo en la lista de líneas y solo entonces comienza la ejecución del bucle for. Puede tomar una cantidad significativa de tiempo y debe pensarlo dos veces antes de usarlo en archivos de texto grandes.

Conclusión

Hay múltiples formas de leer un archivo línea por línea en Java, y la selección del enfoque apropiado es enteramente una decisión del programador. Debe pensar en el tamaño de los archivos que planea procesar, los requisitos de rendimiento, el estilo del código y las bibliotecas que ya están en el proyecto. Asegúrese de probar en algunos casos de esquina como archivos grandes, vacíos o inexistentes, y estará listo para continuar con cualquiera de los ejemplos proporcionados.

Licensed under CC BY-NC-SA 4.0