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.