Hvordan lese en stor fil effektivt med Java
1. Oversikt
Denne opplæringen vil vises hvordan du leser alle linjene fra en stor fil i Java på en effektiv måte.
Denne artikkelen er en del avJava - Tilbake til Basic”Tutorial her på Baeldung.
2. Lesing i minnet
Standard måten å lese linjene i filen er i minnet - både Guava og Apache Commons IO gir en rask måte å gjøre nettopp det:
Files.readLines (ny fil (sti), Charsets.UTF_8);
FileUtils.readLines (ny fil (bane));
Problemet med denne tilnærmingen er at alle fillinjene holdes i minnet - noe som raskt vil føre til OutOfMemoryError hvis filen er stor nok.
For eksempel - leser en ~ 1 GB fil:
@Test offentlig ugyldighet givenUsingGuava_whenIteratingAFile_thenWorks () kaster IOException {Stringsti = ... Files.readLines (ny fil (sti), Charsets.UTF_8); }
Dette starter med at en liten mengde minne blir brukt: (~ 0 Mb forbrukt)
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Totalt minne: 128 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Gratis minne: 116 Mb
Derimot, etter at hele filen er behandlet, har vi på slutten: (~ 2 GB forbrukt)
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Totalt minne: 2666 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Gratis minne: 490 Mb
Noe som betyr at omtrent 2,1 GB minne forbrukes av prosessen - årsaken er enkel - linjene i filen lagres alle i minnet nå.
Det bør være åpenbart ved dette punktet at å lagre innholdet i filen vil raskt tømme det tilgjengelige minnet - uavhengig av hvor mye det faktisk er.
Hva mer, vi trenger vanligvis ikke alle linjene i filen i minnet på en gang - i stedet trenger vi bare å kunne gjenta gjennom hver enkelt, gjøre litt behandling og kaste den. Så dette er nøyaktig hva vi skal gjøre - gå gjennom linjene uten å holde dem alle i minnet.
3. Streaming gjennom filen
La oss nå se på en løsning - vi skal bruke en java.util.Scanner for å kjøre gjennom innholdet i filen og hente linjer serievis, en etter en:
FileInputStream inputStream = null; Skanner sc = null; prøv {inputStream = ny FileInputStream (sti); sc = ny skanner (inputStream, "UTF-8"); mens (sc.hasNextLine ()) {Strenglinje = sc.nextLine (); // System.out.println (linje); } // merk at skanneren undertrykker unntak hvis (sc.ioException ()! = null) {throw sc.ioException (); }} til slutt {if (inputStream! = null) {inputStream.close (); } hvis (sc! = null) {sc.close (); }}
Denne løsningen vil gjentas gjennom alle linjene i filen - muliggjør behandling av hver linje - uten å beholde referanser til dem - og avslutningsvis, uten å ha dem i minnet: (~ 150 Mb forbrukt)
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Totalt minne: 763 Mb [main] INFO org.baeldung.java.CoreJavaIoUnitTest - Gratis minne: 605 Mb
4. Streaming med Apache Commons IO
Det samme kan oppnås ved å bruke Commons IO-biblioteket ved å bruke tollen LineIterator levert av biblioteket:
LineIterator it = FileUtils.lineIterator (theFile, "UTF-8"); prøv {while (it.hasNext ()) {Strenglinje = it.nextLine (); // gjør noe med linje}} til slutt {LineIterator.closeQuietly (it); }
Siden hele filen ikke er fullt i minnet - vil dette også resultere i ganske konservative tall for minneforbruk: (~ 150 Mb forbrukt)
[main] INFO o.b.java.CoreJavaIoIntegrationTest - Totalt minne: 752 Mb [main] INFO o.b.java.CoreJavaIoIntegrationTest - Gratis minne: 564 Mb
5. Konklusjon
Denne raske artikkelen viser hvordan du gjør det behandle linjer i en stor fil uten iterativt, uten å tømme det tilgjengelige minnet - som viser seg ganske nyttig når du arbeider med disse store filene.
Implementeringen av alle disse eksemplene og kodebiter kan du finne i GitHub-prosjektet vårt - dette er et Maven-basert prosjekt, så det skal være enkelt å importere og kjøre som det er.