Java. Leer archivos grandes eficientemente.

por | enero 15, 2014

Introducción

Este tutorial mostrará como leer un archivo bastante pesado(largo) de una manera eficiente.

Leyendo en memoria

La forma estándar de leer las líneas de un archivo es en memoria, ambas librerías Guava y Apache Commons realizan esta tarea de una manera muy sencilla:

Files.readLines(new File(path), Charsets.UTF_8);
FileUtils.readLines(new File(path));

El problema con este enfoque es que todas las líneas son mantenidas en memoria, lo cual provocará fácilmente un error OutOfMemoryError si el archivo es demasiado grande.

Por ejemplo al leer un archivo de 1Gb:

@Test
public void givenUsingGuava_whenIteratingAFile_thenWorks() 
    throws IOException {
    String path = ...
    Files.readLines(new File(path), Charsets.UTF_8);
}

Al inicio empieza con pequeño consumo de memoria:

[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 128 Mb
[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 116 Mb

Sin embargo, luego de que todo el archivo ha sido procesado:

[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 2666 Mb
[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 490 Mb

Lo que significa que alrededor de 2.1 Gb de memoria son consumidas por el proceso – la razón es simple – ahora todas las lineas del archivo están almacenadas en memoria.

Parece obvio que al mantener en memoria todo el contenido del archivo, agotará rápidamente la memoria disponible, aunque dispongamos de mucha memoria.

De echo, nosotros no necesitamos tener todas las líneas del fichero cargadas en memoria, en su lugar, nosotros necesitamos poder ser capaces de recorrer línea a línea con algo de procesamiento y luego desecharlo. Eso es lo que vamos ha hacer, iterar por cada línea sin tener la necesidad de almacenarlo en memoria.

Haciendo “Streaming” por el archivo

Tomen en cuenta la solución, vamos a usar la clase java.util.Scanner para correr por el contenido del archivo para recuperar las líneas en serie, una a una:

FileInputStream inputStream = null;
Scanner sc = null;
try {
    inputStream = new FileInputStream(path);
    sc = new Scanner(inputStream, "UTF-8");
    while (sc.hasNextLine()) {
        String line = sc.nextLine();
        // System.out.println(line);
    }
    // note that Scanner suppresses exceptions
    if (sc.ioException() != null) {
        throw sc.ioException();
    }
} finally {
    if (inputStream != null) {
        inputStream.close();
    }
    if (sc != null) {
        sc.close();
    }
}

Esta solución itera a través de todas las líneas en el archivo, permitiendo el procesamiento de cada línea, sin mantener referencias a el, lo que quiere decir sin mantenerlo en memoria.

Conclusión

Este pequeño artículo muestra como procesar líneas de un archivo grande sin agotar la memoria disponible, lo que resulta muy útil cuando se trabaja con este tipo de archivos.

Fuente: http://www.baeldung.com/java-read-lines-large-file