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'è.