Come leggere un file di grandi dimensioni in modo efficiente con Java

1. Panoramica

Questo tutorial mostrerà come leggere tutte le righe da un file di grandi dimensioni in Java in modo efficiente.

Questo articolo fa parte del tutorial " Java - Back to Basic " qui su Baeldung.

2. Leggere in memoria

Il modo standard di leggere le righe del file è nella memoria: sia Guava che Apache Commons IO forniscono un modo rapido per farlo:

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

Il problema con questo approccio è che tutte le righe del file vengono mantenute in memoria, il che porterà rapidamente a OutOfMemoryError se il file è abbastanza grande.

Ad esempio, leggendo un file da ~ 1 GB :

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

Questo inizia con una piccola quantità di memoria consumata: (~ 0 Mb consumati)

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

Tuttavia, dopo che l'intero file è stato elaborato , abbiamo alla fine: (~ 2 Gb consumati)

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

Ciò significa che circa 2,1 GB di memoria vengono consumati dal processo - il motivo è semplice - le righe del file vengono tutte archiviate in memoria ora.

Dovrebbe essere ovvio a questo punto che mantenere in memoria il contenuto del file esaurirà rapidamente la memoria disponibile , indipendentemente da quanto sia effettivamente.

Inoltre, di solito non abbiamo bisogno di tutte le righe del file in memoria contemporaneamente - invece, dobbiamo solo essere in grado di iterare ciascuna di esse, eseguire alcune elaborazioni e buttarle via. Quindi, questo è esattamente ciò che faremo: iterare attraverso le linee senza tenerle tutte in memoria.

3. Streaming attraverso il file

Vediamo ora una soluzione: utilizzeremo java.util.Scanner per eseguire il contenuto del file e recuperare le righe in serie, una per 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(); } }

Questa soluzione itererà attraverso tutte le righe del file - consentendo l'elaborazione di ogni riga - senza mantenere i riferimenti ad esse - e in conclusione, senza tenerle in memoria : (~ 150 Mb consumati)

[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 763 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 605 Mb

4. Streaming con Apache Commons IO

Lo stesso può essere ottenuto utilizzando anche la libreria Commons IO, utilizzando il LineIterator personalizzato fornito dalla libreria:

LineIterator it = FileUtils.lineIterator(theFile, "UTF-8"); try { while (it.hasNext()) { String line = it.nextLine(); // do something with line } } finally { LineIterator.closeQuietly(it); }

Poiché l'intero file non è completamente in memoria, ciò comporterà anche numeri di consumo di memoria piuttosto conservativi : (~ 150 Mb consumati)

[main] INFO o.b.java.CoreJavaIoIntegrationTest - Total Memory: 752 Mb [main] INFO o.b.java.CoreJavaIoIntegrationTest - Free Memory: 564 Mb

5. conclusione

Questo rapido articolo mostra come elaborare le righe in un file di grandi dimensioni senza iterativamente, senza esaurire la memoria disponibile , il che si rivela molto utile quando si lavora con questi file di grandi dimensioni.

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel nostro progetto GitHub : questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.