# Java - Sektion aus großer Datei extrahieren



## Festplatte (3. September 2014)

*Java - Sektion aus großer Datei extrahieren*

Moin, ich beschäftige mich seit etwa 2 Tagen mit Java und möchte nun ein kleines Programm schreiben, das mir aus einer großen Datei eine Sektion zwischen zwei bestimmten Zeilen raussucht und diese in eine neue Datei schreibt. Die Quell-Datei ist etwa 900 MB groß. Aus dieser möchte ich nun den Text zwischen "/start xxxx" dem nächsten "/start yyyy" raussuchen und diesen in eine neue Datei schreiben. Der String xxxx ist vorher vom Nutzer festgelegt, der String yyyy wird nicht vom Nutzer definiert und soll hier jetzt einfach das nächste "/start" symbolisieren. Hier ein Beispiel:


```
blablablablabla

/start baum

blubblubblubblub

/start banane

blablablablabla
```

Die vom Programm erstellte Datei sollte also den Inhalt "blubblubblubblub" haben. Ich habe bis jetzt schon ein kleines Programm geschrieben, das eine Datei einlesen, in eine ArrayList kopieren und wieder als Datei schreiben kann:


```
public class Test
{
	public static void main(String[] args)
	{
		if (args.length == 0)
		{
			userInput();
		}
		else
		{
			if (args.length == 2)
			{
				String sourceNameArg = args[0];
				String databaseNameArg = args[1];
				readFile(sourceNameArg, databaseNameArg);
			}
		}
	}

	[..userInput..]

	public static void readFile(String sourceName, String databaseName)
	{
		File sourceFile = new File(sourceName);

		try (BufferedReader input = new BufferedReader(new FileReader(sourceFile)); Scanner scanner = new Scanner(input))
		{
			ArrayList<String> textArray = new ArrayList<>();
			long startTime = System.currentTimeMillis();

			while (scanner.hasNextLine())
			{
				String line = scanner.nextLine();
				textArray.add(line);
			}

			System.out.println("Elapsed: " + (System.currentTimeMillis() - startTime) + " ms");
			writeFile(textArray, databaseName);
		}
		catch (IOException e)
		{
			System.out.println("'" + sourceName + "' not found.");
			userInput();
		}
	}

	public static void writeFile(ArrayList<String> text, String targetName)
	{
		try (PrintWriter writer = new PrintWriter(new FileWriter(targetName + ".dmp")))
		{
			Iterator<String> iterator = text.iterator();

			while (iterator.hasNext())
			{
				Object o = iterator.next();
				writer.println(o);
			}
		}
		catch (IOException e)
		{
			System.out.print("Something went wrong.");
		}
	}
}
```

Bei kleinen Dateien kein Problem, bei einer 800 MB Datei stürzt der Spaß während dem Einlesen mit einem OutOfMemory ab. Klar, 800 MB in ein Array zu hauen braucht nun mal ordentlich Speicher. Nun zu meinen Fragen:

1) Ist es grundsätzlich richtig, den Text mittels BufferedReader(new FileReader(sourceFile)) und einem Scanner einzulesen oder geht das noch besser?

2) Brauche ich für das Extrahieren einer bestimmten Sektion überhaupt das Array oder kann ich die neue Datei direkt schreiben? Wenn ja, wie?

3) Wie genau kann ich das eigentliche Extrahieren der Sektion realisieren, vom "/start meinString" bis hin zum nächsten "/start"?


----------



## Rho (3. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

So oder so ähnlich könnte man es machen:


```
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class Extractor {	

	public static void main(String[] args) {
		if (args.length != 3) {
			System.out.println("Ungültige Anzahl an Argumenten.");
			return;
		}

		String source = args[0];
		String destination = args[1];
		String section = args[2];

		ExtractSection(source, destination, section);
	}

	private static void ExtractSection(String source, String destination, String section) {
		try (BufferedReader reader = new BufferedReader(new FileReader(source));
				PrintWriter writer = new PrintWriter(new FileWriter(destination))) {
			SkipToSection(section, reader);
			CopySectionData(reader, writer);
		} catch (FileNotFoundException e) {
			System.out.println("Quelldatei nicht gefunden.");
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}

	private static void SkipToSection(String section, BufferedReader reader) throws IOException {
		String line = reader.readLine();
		String sectionMarker = "/start " + section;

		while (line != null && !line.startsWith(sectionMarker))
			line = reader.readLine();
	}

	private static void CopySectionData(BufferedReader source,	PrintWriter destination) throws IOException {
		String line = source.readLine();
		
		while (line != null && !line.startsWith("/start")) {
			destination.println(line);
			line = source.readLine();
		}
	}

}
```


----------



## crusherd (3. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Hi,

eine kleine Quick-and-Dirty Lösung:


```
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Test
{
    public static void main(final String[] args) throws IOException
    {
        final BufferedReader reader = getBufferedReader("file");
        final BufferedWriter writer = getBufferedWriter("file");

        copyContent(reader, writer, "marker");

        writer.flush();
        writer.close();
        reader.close();
    }

    private static BufferedReader getBufferedReader(final String sourceName) throws FileNotFoundException {
        return new BufferedReader(new InputStreamReader(new FileInputStream(sourceName)));
    }

    private static BufferedWriter getBufferedWriter(final String file) throws FileNotFoundException {
        return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
    }

    private static void copyContent(final BufferedReader reader, final BufferedWriter writer, final String marker) throws IOException {
        String line = reader.readLine();
        while((line != null) && !line.equalsIgnoreCase("/start" + marker)) {
            line = reader.readLine();
        }

        line = reader.readLine();
        while((line != null) && !line.startsWith("/start")) {
            writer.append(line);
            writer.newLine();
            line = reader.readLine();
        }
    }
}
```

Entspricht das deiner Vorstellung? Userinput müsste halt noch hinzugefügt werden.

Gruß

crusherd

EDIT: Mist, zu langsam...


----------



## Festplatte (3. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Die UserInput-Funktion ist bereits fertig. Ich probiere morgen mal beide Methoden aus und berichte, danke schon mal.


----------



## Rho (5. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Na, klappts?


----------



## Festplatte (6. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Ganz vergessen.  Jo, funktioniert.  Danke.


----------



## Festplatte (7. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Push - Ich versuche gerade für die Anwendung noch einen Zip-Import zu implementieren. Der Nutzer gibt den Namen der .zip, den Namen der .txt-Datei in dem .zip und den Namen der heraus zu kopierenden Sektion ein. Der Code für das Verarbeiten und den .zip-Import sieht momentan so aus:


```
private static void zipImport(String zipSourceName, String zipContentName, String sectionName)
	{
		try (ZipFile zip = new ZipFile(zipSourceName))
		{
			ZipEntry content = new ZipEntry(zipContentName);

			if (!content.isDirectory())
			{
				InputStream input = zip.getInputStream(content);
				BufferedReader inputReader = new BufferedReader(new InputStreamReader(input));
				PrintWriter outputWriter = new PrintWriter(new FileWriter(sectionName + ".dmp"));

				verarbeiteDatei(inputReader, outputWriter, sectionName);
			}
		}
		catch (FileNotFoundException e)
		{
			System.out.println("'" + zipSourceName + "' not found.");
		}
		catch (NullPointerException e)
		{
			System.out.println("'" + zipContentName + "' not found.");
		}
	}

        private static void verarbeiteDatei(BufferedReader inputReader, PrintWriter outputWriter, String sectionName)
	{
		String line = "";
		String searchWord = "/start ";
		String sectionStart = searchWord + sectionName;
		try
		{
			while (line != null && !line.equals(sectionStart))
			{
				line = inputReader.readLine();
			}

			line = inputReader.readLine();

			while (line != null && !line.startsWith(searchWord))
			{
				outputWriter.println(line);
				line = inputReader.readLine();
			}
		}
	}
```

Dieser Code kann die .zip und die darin enthaltene Datei zwar lesen, allerdings fehlen teilweise einige Zeilen, je nach Position der Sektion. Heißt: Wenn in meiner Quell-Datei in der 1. Zeile /start baum steht und ich das nun aus der .txt in der .zip raushaben möchte, fehlen in der vom Programm erstellten Datei am Ende etwa 6 Zeilen der Sektion. Liegt /start baum in der Quell-Datei z.B. in Zeile 20000, dann fehlen am Ende gleich mehrere 100 Zeilen. Ich kann mir gerade bei bestem Willen nicht erklären, warum dem so ist. Jemand Vorschläge?


----------



## Rho (7. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Meine Vermutung ist, dass nicht alles aus dem Schreibpuffer des PrintWriters in die Ausgabedatei geschrieben wird, bevor sich das Programm beendet, da du ihn niemals schließt und auch keinen "Flush" durchführst.


----------



## Festplatte (7. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Also einfach zu dem try-Block der zipImport() noch einen finally packen und dann schließen? Probiere ich gleich mal aus, danke schon mal!


----------



## Rho (7. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Erfolg gehabt damit?


----------



## crusherd (7. September 2014)

Am Besten noch ein flush() vor dem Schließen, nur um sicher zu gehen.


----------



## Rho (7. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Und was soll das bringen?


----------



## Festplatte (8. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*



Rho schrieb:


> Erfolg gehabt damit?



Jo, vielen Dank!


----------



## crusherd (10. September 2014)

Hi,

Sorry für die etwas späte Antwort. 
Mit einem flush() werden die restlichen Daten in die Datei geschrieben, die momentan im Puffer sind.
Ein close() schließt meines Wissens nach nur die Datei. Oder wird intern auch zuerst ein flush() durchgeführt? 
In der Javadoc steht dazu leider nichts konkreteres.

Gruß
crusherd


----------



## Rho (10. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*



			
				http://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html#close-- schrieb:
			
		

> Closes the stream. This is done by *flushing the stream* and then closing the underlying output stream.



Ist doch ziemlich konkret.


----------



## crusherd (10. September 2014)

Jop, hast vollkommen recht. Hab da was falsch in Erinnerung gehabt und in einer anderen Klasse nachgeschaut (PrintWriter).
Siehe http://docs.oracle.com/javase/7/docs/api/index.html?java/io/PrintStream.html


----------



## Rho (10. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Verhält sich bei PrintStream aber auch nicht anders.



			
				http://docs.oracle.com/javase/7/docs/api/java/io/PrintStream.html#close%28%29 schrieb:
			
		

> Closes the stream. This is done by flushing the stream and then closing the underlying output stream.



*Update:*

Ist natürlich blödsinn. Hatte nicht darauf geachtet, dass du zwar von PrintWriter schreibst, aber die Dokumentation zu PrintStream verlinkt hast.


----------



## XPrototypeX (11. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

Bei größeren Dateien würde sich NIO anbieten. Das ist gerade bei großen Datensätzen viel performanter. 

Die Antwort bei Stackoverflow illustriert ein gutes Beispiel: 

Java NIO Issue on CharBuffer - Stack Overflow


----------



## crusherd (11. September 2014)

Rho schrieb:


> Verhält sich bei PrintStream aber auch nicht anders.
> 
> Update:
> 
> Ist natürlich blödsinn. Hatte nicht darauf geachtet, dass du zwar von PrintWriter schreibst, aber die Dokumentation zu PrintStream verlinkt hast.



Da hat mir die copy&paste funktion auf dem Smartphone dazwischen gefunkt. Die Seite mit Printwriter war offen, aber die Url hat immer noch auf PrintStream gezeigt. 

Gruß
crusherd


----------



## Rho (11. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*



XPrototypeX schrieb:


> Bei größeren Dateien würde sich NIO anbieten. Das ist gerade bei großen Datensätzen viel performanter.



Wäre mir neu. Kannst du das irgendwie durch Benchmarks belegen oder wenigstens logisch begründen?

Kann mich aber natürlich auch irren. Habe die letzten Jahre kaum etwas mit Java gemacht.


----------



## Festplatte (11. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

5 Sekunden für eine 800 MB Datei finde ich noch erträglich.  Ich sehs mir trotzdem mal an.


----------



## bingo88 (12. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*

AFAIK bezieht sich das auf NIO.2 (mit Java 7 eingeführt). Das betrifft aber im Wesentlichen die Unterstützung von Dateisystemabstraktionen (sprich: nichtlokale Dateien über dieselbe API wie lokale Dateien). In Bezug auf Datei-IO bringt das aber nicht unbedingt Vorteile, zumindest wenn man sich nicht zu doof anstellt. Man kann sogar Szenarien basteln, wo NIO Datei-IO deutlich langsamer als klassisches Datei-IO ist.


----------



## nay (12. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*



Festplatte schrieb:


> 5 Sekunden für eine 800 MB Datei finde ich noch erträglich.  Ich sehs mir trotzdem mal an.


 
Mit welchem Festspeicher hast du das getestet?


----------



## XPrototypeX (15. September 2014)

*AW: Java - Sektion aus großer Datei extrahieren*



Rho schrieb:


> Wäre mir neu. Kannst du das irgendwie durch Benchmarks belegen oder wenigstens logisch begründen?
> 
> Kann mich aber natürlich auch irren. Habe die letzten Jahre kaum etwas mit Java gemacht.


 
Ich hab etwas gegooglt und 2 Benchmarks gesehen, auf denen I/O etwas schneller als NI/O ist, allerdings auch einen wo es genau umgekehrt ist. Generell konnte ich aber festestellen das NI/O, wie in einem Firmen internen benchmark gezeigt hat besonders bei mehreren unterschiedlichen I/Os schneller ist, da es eben nicht blockiert wie ein Stream. 
Ein weiterer Vorteil ist das man direkt vom OS speicher anfordern kann, das das OS auch selbst verwaltet (daher der speicher befindet sich nicht auf dem java Heap). Das beschleunigt natürlich lesen und schreib vorgänge auf diesen Speicher. 

Der wohl am ausagekräftigste Benchmark ist folgender: 
Java tip: How to read files quickly | Nadeau Software 

Ich selbst benutze eigentlich immer NIO finde es irgendwie angenehmer zu nutzen.


----------

