Sådan læses en stor fil effektivt med Java

1. Oversigt

Denne tutorial vises hvordan man læser alle linjerne fra en stor fil i Java på en effektiv måde.

Denne artikel er en del af "Java - Tilbage til Basic”Tutorial her på Baeldung.

2. Læsning i hukommelsen

Standardmetoden til at læse filens linjer er i hukommelsen - både Guava og Apache Commons IO giver en hurtig måde at gøre netop det på:

Files.readLines (ny fil (sti), Charsets.UTF_8);
FileUtils.readLines (ny fil (sti));

Problemet med denne tilgang er, at alle fillinjer opbevares i hukommelsen - hvilket hurtigt vil føre til OutOfMemoryError hvis filen er stor nok.

For eksempel - læser en ~ 1 GB fil:

@Test offentlig ugyldighed givenUsingGuava_whenIteratingAFile_thenWorks () kaster IOException {String sti = ... Files.readLines (ny fil (sti), Charsets.UTF_8); }

Dette starter med at der forbruges en lille mængde hukommelse: (~ 0 Mb forbrugt)

[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Samlet hukommelse: 128 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Gratis hukommelse: 116 Mb

Imidlertid, efter at den fulde fil er behandlet, vi har i slutningen: (~ 2 GB forbrugt)

[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Samlet hukommelse: 2666 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Gratis hukommelse: 490 Mb

Hvilket betyder, at cirka 2,1 GB hukommelse forbruges af processen - årsagen er enkel - filens linjer gemmes alle i hukommelsen nu.

Det skal være indlysende ved dette punkt, at at gemme indholdet af filen vil hurtigt udtømme den tilgængelige hukommelse - uanset hvor meget det faktisk er.

Hvad mere er, vi har normalt ikke brug for alle linjerne i filen i hukommelsen på én gang - i stedet er vi bare nødt til at være i stand til at gentage gennem hver enkelt, udføre noget behandling og smide det væk. Så dette er præcis, hvad vi skal gøre - gentag gennem linjerne uden at holde dem alle i hukommelsen.

3. Streaming gennem filen

Lad os nu se på en løsning - vi skal bruge en java.util.Scanner for at køre gennem indholdet af filen og hente linjer serielt, en efter en:

FileInputStream inputStream = null; Scanner sc = null; prøv {inputStream = ny FileInputStream (sti); sc = ny scanner (inputStream, "UTF-8"); mens (sc.hasNextLine ()) {String line = sc.nextLine (); // System.out.println (linje); } // bemærk, at Scanner undertrykker undtagelser, hvis (sc.ioException ()! = null) {throw sc.ioException (); }} endelig {if (inputStream! = null) {inputStream.close (); } hvis (sc! = null) {sc.close (); }}

Denne løsning gentager sig gennem alle linjerne i filen - giver mulighed for behandling af hver linje - uden at beholde referencer til dem - og som konklusion uden at holde dem i hukommelsen: (~ 150 Mb forbrugt)

[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Samlet hukommelse: 763 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Gratis hukommelse: 605 Mb

4. Streaming med Apache Commons IO

Det samme kan også opnås ved hjælp af Commons IO-biblioteket ved hjælp af skikken LineIterator leveret af biblioteket:

LineIterator it = FileUtils.lineIterator (theFile, "UTF-8"); prøv {while (it.hasNext ()) {String line = it.nextLine (); // gør noget med linje}} endelig {LineIterator.closeQuietly (it); }

Da hele filen ikke er helt i hukommelsen - vil dette også resultere i temmelig konservative hukommelsesforbrugsnumre: (~ 150 Mb forbrugt)

[main] INFO o.b.java.CoreJavaIoIntegrationTest - Total hukommelse: 752 Mb [main] INFO o.b.java.CoreJavaIoIntegrationTest - Gratis hukommelse: 564 Mb

5. Konklusion

Denne hurtige artikel viser, hvordan man gør det behandle linjer i en stor fil uden iterativt uden at udtømme den tilgængelige hukommelse - hvilket viser sig ret nyttigt, når du arbejder med disse store filer.

Implementeringen af ​​alle disse eksempler og kodestykker kan findes i vores GitHub-projekt - dette er et Maven-baseret projekt, så det skal være let at importere og køre som det er.