Textdatei blockweise einlesen?

Hallo
Ich habe eine Tab getrennt Textdatei mit Einträgen 1.ID 2.Name zb.

2 Alpha
2 Beta
2 Theta
3 Bmw
3 Audi
usw…

Ich muss die Datei einlesen und aus jeder Zeile ein Objekt machen um später damit arbeiten zu können. Die gebauten Objekte stelle ich in einer ArrayList ab dessen grösse gleich der Anzahl der Einträge ist.
Alles kein Problem, aber die Datei kann unter umständen Millionen von Einträgen haben, manchmal so gross das ich kein ArrayList dieser grösse bauen kaunn ohne eine Exception zu bekommen das der Speicher limit voll ist.

Deswegen haben wir ein Tipp bekommen, das wir die Datei Blockweise einlesen sollen. Z.b erst nur die Einträge mit der ID 1 dann die Einträge mit der ID2 usw.
Genau hier verstehe ich es nicht. Am ende müssen doch sowieso diese Millionen Objekte im Speicher landen ?

Wie setzt man so eine Blockweise einlesung um?

Mein Code bis jetzt

		while ((line = br.readLine()) != null) {
				String[] data= line.split("	");
				addObject(buildObject(data));
				}

			}

Was soll denn später mit den eingelesenen Objekten passieren?

Steht nicht in der Aufgabe, nur “aus den Zeilen sollen Objekte gebaut werden damit später eine einfache Bearbeitung möglich ist”

Was weißt du denn über den Inhalt der Datei?
Sind die Zeilen einer ID immer untereinander?
Sind immer gleich viele Zeilen zu einer ID vorhanden?
Soll je Zeile ein Objekt erstellt werden oder je ID?

man kann ja Hoffnung bei der Frage hin zum Forum haben, aber das Urteil dürfte doch nüchtern sein:
die Beschreibung ist unklar und philosophisch,
vielleicht baut man sich eine Komponente der man befehlen kann
readAllNextId() -> liefert alles mit Id 1

readAllNextId() -> löscht alles bisherige, falls noch Kontrolle darüber, liefert alles mit Id 2

so nie alles gleichzeitig im Speicher, Überlauf verhindert

aber das ist pures Raten,
für verständliche Anleitungen musst du den Aufgabensteller fragen,

kein echtes Java-Problem

Nehmen wir uns dem Probl an.

Pattern (/Matcher) verwende ich eigentlich immer ganz gerne. In .split(regex); ist ja auch nix anderes implementiert. Daher mein Vorschlag:

Test-Datei:

2	ggg
1	eee
3	jjj
1	ddd
3	hhh
3	iiii
0	ccc
0	aaa
0	bbb
1	fff

Quell-text: ```import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**

  • @author CB
    */
    public class ZiffernVorne implements Iterable<List> {

    private static final Pattern PAT = Pattern.compile("^(\d+)\s+(.*)$");
    private final File file;

    public static List read(int index, File file) throws IOException {
    List ls = new ArrayList();
    BufferedReader br = new BufferedReader(new FileReader(file));
    String lin;
    while ((lin = br.readLine()) != null) {
    Matcher mat = PAT.matcher(lin);
    if (mat.find()) {
    try {
    int i = Integer.parseInt(mat.group(1));
    if (i == index) {
    ls.add(mat.group(2));
    }
    } catch (NumberFormatException nfe) { // ignore
    }
    }
    }
    br.close();
    return ls;
    }

    public ZiffernVorne(File file) {
    if (file == null) {
    throw new IllegalArgumentException(“file is null”);
    }
    this.file = file;
    }

    @Override
    public Iterator<List> iterator() {
    return new Iterator<List>() {
    int idx = 0; // or 1
    List lis;

         @Override
         public boolean hasNext() {
             try {
                 lis = read(idx, file);
                 return !lis.isEmpty();
             } catch (IOException ioe) {
                 return false;
             }
         }
    
         @Override
         public List<String> next() {
             idx++;
             return lis;
         }
    
         @Override
         public void remove() {
             throw new UnsupportedOperationException("Not supported yet.");
             /*ls = null;
              i--;*/
         }
     };
    

    }

    public static void main(String[] args) {
    for (List list : new ZiffernVorne(new File(“C:\Users\irgend-wer\Desktop\Neues Textdokument (2).txt”))) {
    System.out.println("list = " + list);
    }
    }
    }```

Ausgabe:

// hier darf jetzt jeder einmal selbst schauen.

Was gilt denn jetzt? Im RAM liegen immer nur so viele Zeilen, wie zu genau einem Index gehören (+ 1). Kann man Iterator nicht intelligerner machen? Ja, das funkt, s. d. z. B. auch removed werden kann, einer anonymen inneren Klasse kann aber nix übergeben werden.

Verstehst du das jetzt alles?

Alternativ musst du dann immer bis (Ziffer+) lesen, group(1) HashMap<Integer, List<String>> hinzufügen und bei Index i nur alle Zeilen mit i lesen, nur, wo fängt denn so eine Zeile i an?
Ich kann viele Fragen stellen, was? :wink: Grüße, Cyborg.

Das Ablegen in einer ArrayList scheint ja nicht Bestandteil der Aufgabe zu sein. Deswegen überlegen, ob man überhaupt alles auf einmal in einer List haben muss. Vielleicht reicht es ja, das Processing Zeile für Zeile/Objekt für Objekt aufzuziehen. Da bietet sich ein Iterator und dann in Java 8 die Stream-API an.

Durch die Reihen gehen.
Als String auslesen und dann if Abfrage startsWith("i")

Am besten noch ne for schleife für alle ids

Neben den Indexes braucht man aber noch Anfang Ende aller Zeilen, HashMap index zeilenAnf zeilenEnd und RandAccess, <-> das verringert nicht den benötigten Arbeitsspeicher, aber das Programm sollte eigentlich schneller werden, ohne in jedem Schleife das komplette File zu lesen. Es kommt auf die Größe an.
“google” macht ja quasi nix anderes.
BufReader wird wahrscheinlich keine Methode habn Zeichen- Index, dann ist das schon schwieriger, oder was meint ihr?

Nochmal, es scheint ja nicht Bestandteil der Aufgabe zu sein, ALLE Objekte in einer Collection/List/Map zu haben. Im Gegenteil würde ich sogar vermuten, dass das gerade NICHT gefordert ist, wenn die Datei Millionen Zeilen haben kann. Die Bearbeitung muss also je Zeile/Objekt funktionieren. Das geht elegant mit Iteratoren und dem Stream-API. Zunächst also der Iterator:

import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Implementation iterates over lines read from {@link #input}.
 * 
 * @author nehr
 */
public final class IteratorOverLines implements Iterator<String> {

	final BufferedReader input;

	String nextLine;

	/**
	 * Creates an {@link Iterator} over the lines read from a
	 * {@link BufferedReader}
	 * 
	 * @param input
	 *            the {@link BufferedReader} to read from, cannot be null,
	 *            cannot be closed
	 */
	public IteratorOverLines(BufferedReader input) {
		this.input = input;
		nextLine = readUnchecked();
	}

	@Override
	public boolean hasNext() {
		return nextLine != null;
	}

	@Override
	public String next() {
		if (nextLine == null) {
			throw new NoSuchElementException(
					"Input has reached end. No more lines to read.");
		}
		String result = nextLine;
		nextLine = readUnchecked();
		return result;
	}

	String readUnchecked() {
		try {
			return input.readLine();
		} catch (IOException e) {
			closeUnchecked();
			throw new IllegalStateException(e);
		}
	}

	void closeUnchecked() {
		try {
			input.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

Damit hat man die einzelnen Zeilen zu fassen. Von hier aus kann die Bearbeitung und Umwandlung in das gewünschte Zielobjekt starten. Mit der Stream-API würde man es so machen:

Iterator<String> linesIter = new IteratorOverLines(input);
Spliterator<String> linesSpliter = Spliterators.spliteratorUnknownSize(linesIter, Spliterator.IMMUTABLE & Spliterator.NONNULL);
// -Es wird ein Stream von Strings (den Zeilen der Datei) erzeugt.
// -Jede Zeile wird an Leerzeichen gesplittet 'map((line) -> line.split(" "))', Ergebnis ein Stream von String[]
// -Ausgehend davon, dass MyDesiredObject einen Constructor hat, der mit String[] als Parameter umgehen kann,
//  wird aus jedem String[] eine Instanz von MyDesiredObject 'map((strings) -> MyDesiredObject::new).', Ergebnis ein neuer Stream<MyDesiredObject>
// -Falls gewünscht, kann man noch filtern. Ausgehend davon, dass die id als int vorliegt und es eine Methode MyDesiredObject#getId() gibt,
//  hier ein Filter, der nur Objekte mit der id 1 durchlässt 'filter((myObj) -> myObj.getId() == 1)'
Stream<MyDesiredObject> myObjStream = StreamSupport.stream(linesSpliter, false)
    .map((line) -> line.split(" "))
    .map((strings) -> MyDesiredObject::new)
    .filter((myObj) -> myObj.getId() == 1);

Ohne Stream-API würde man direkt den Iterator für Iteration benutzen. Innerhalb des jedes Iterationsschrites die Zeilen splitten, damit gewünschte Objekte erzeugen. Filterung wäre sicher auch möglich, aber etwas krampfig.

@nillehammer

Den Iterator kannst du dir schenken, der BufferedReader hat neuerdings eine “lines()”-Methode die einen Stream zurückliefert.

Japp, mit der lines-Methode wird’s ja noch viel einfacher. Dann den Iterator also nur verwenden, wenn noch kein Java 8 verfügbar ist!

OT: Hab mir auch gerade den Quellcode der Methode angeschaut und bin dabei auf die UncheckedIOException gestoßen. Werde in Zukunft immer die nehmen, anstatt wie bisher die IllegalStateException. Außerdem dazugelernt, dass Spliterator.ORDERED nicht (wie ich bisher dachte) bedeutet, dass es eine Ordnung gibt, sondern dass die Reihenfolge der Elemente beibehalten wird. Dein Tipp hat sich also für mich mehrfach gelohnt :slight_smile: Ach ja und einen Bug habe ich in meinem Code auch noch, die int-Masken müssen natürlich geodert werden und nicht geundet.

Ich hab’s mal umgeschrieben.

Verwendet wird jetzt LinkedList (ist für diesen Fall glaub’ ich etwas schneller), HashMap (keys sind [quasi] unsortiert!!!), anderer Pattern (nach der Zahl muss es kein space geben), das Proggi sollte jetzt schneller sein.

Die Ausgabe ist die gleiche.

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author CB
 */
public class Ziff implements Iterable<List<String>> {

    private static final Pattern pat = Pattern.compile("^(\\d+)\\s*(.*)$"); // or + + or ++ ++ or + * or ++ *+
    private final File file;
    private final int start;
    private final HashMap<Integer, LinkedList<Integer>> hmilli = new HashMap<Integer, LinkedList<Integer>>();

    public Ziff(File file, int start) throws IOException {
        if (file == null) {
            throw new IllegalArgumentException("file is null");
        }
        this.file = file;
        this.start = start;
        initHmilli();
    }

    private void initHmilli() throws IOException {
        int linIdx = 0;
        BufferedReader br = new BufferedReader(new FileReader(file));
        String lin;
        while ((lin = br.readLine()) != null) {
            Matcher mat = pat.matcher(lin);
            if (mat.find()) {
                try {
                    int i = Integer.parseInt(mat.group(1));
                    if (hmilli.containsKey(i)) {
                        hmilli.get(i).add(linIdx);
                    } else {
                        hmilli.put(i, new LinkedList<Integer>(Arrays.asList(linIdx)));
                    }
                } catch (NumberFormatException nfe) { // ignore
                }
            }
            linIdx++;
        }
        br.close();
    }

    private List<String> read(int index) throws IOException { // or public static
        List<String> ls = new LinkedList<String>();
        int linIdx = 0;
        BufferedReader br = new BufferedReader(new FileReader(file));
        for (Integer integer : hmilli.get(index)) {
            for (;;) {
                String lin = br.readLine();
                if (linIdx++ == integer) {
                    Matcher mat = pat.matcher(lin);
                    if (mat.find() && ls.add(mat.group(2)))
                        ;
                    break;
                }
            }
        }
        br.close();
        return ls;
    }

    @Override
    public Iterator<List<String>> iterator() {
        return new Iterator<List<String>>() {
            Iterator<Integer> illi = hmilli.keySet().iterator();

            @Override
            public boolean hasNext() {
                return illi.hasNext();
            }

            @Override
            public List<String> next() {
                try {
                    return read(illi.next());
                } catch (IOException ioe) {
                    return null;
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("Not supported yet.");
                /*idx--;
                 lis = null;*/
            }
        };
    }

    public static void main(String[] args) throws IOException {
        for (List<String> list : new Ziff(new File("C:\\Users\\irgend-wer\\Desktop\\Neues Textdokument (2).txt"), 0)) {
            System.out.println("list = " + list);
        }
    }
}```

'Ne paar Probls gibt es schon noch. 1. BufferedReader sucht nach '
', '
' oder '\r' (plattformabhängig), 2. Datei-position sagt er nicht.

Deswegen kommt man um ZeichenFürZeichenLesen (und dann RandAccess) nicht d'rumherum.

Java 8 hab ich leider nicht. Aber man braucht sowohl alle Zeilen als auch alle Positionen und beides gleichzeitig kann BufferedReader nicht.

Wie findet ihr das? Kritik?