Daten zufällig Einlesen aus einer Datei (binäres Format)

Michelangelo

Mitglied
Hallo,
ich sitze nun seit vielen Stunden an einer Aufgabe und brauche mal wieder einen Denkanstoß.

Ändern Sie das Hangman-Spiel so, dass das Wort nicht mehr zufällig aus einem Array ermittelt wird, sondern aus der Datei, die Sie mit den Methoden datenLesen()/datenSchreiben erstellt haben. Das Wort soll aber nach wie vor zufällig ermittelt werden.

Was mir klar scheint:
1)Es soll ein Wahlfreier Zugriff auf dieser Datei erfolgen.
2) Benötigt wird eine Variable die die Positionierung angibt.
3) Mittels der Klasse Random ist es möglich eine zufällige Zahl an die Variable(für Positionierung) zu übergeben.
Hierbei ist erforderlich: Das der Start für den Zugriff an der Position 4(Byte) beginnt, da auf den vorherigen Bytes die Daten(Zahlen) der Positionierungen befinden.


Mein Problem: Ich habe mir lange Zeit den Kopf zerbrochen und egal was mir auch eingefallen ist( und ich bin kreativ 🙂)
nichts hat mich annähernd zu meiner Lösung gebracht. Entweder wurde Nur der Wert/Stelle der Positionierung zufällig ausgegeben mit jeweils des ersten geschriebenen Wort der Datei. Oder es kam Hauptsächlich Datenmüll beim Zugriffsversuch raus.
Wie kriege ich es hin, dass ich den "Zufallsgenerator" auf bestimmte Werte eingrenzen kann?

Folgend ist mein code mit der Methode datenZufällig().
Java:
public void datenZufall() {
        try(RandomAccessFile datei = new RandomAccessFile("woerter.bin", "r")) {
            //für das Positionieren
            long zufall = 0;
            Random i = new Random();
//10 Werte sollen abgedeckt sein
            zufall = i.nextLong(10);   
            //ist die neue Position gültig?
            if (zufall <= datei.length()-4)
                //dann positionieren
                datei.seek(zufall);
                //lesen und anzeigen
                add(new JLabel("Wert an Position: " + zufall));
                add(new JLabel(datei.readUTF()));
                //zahl.setText(Integer.toString(anzahlWoerter));
            pack();
        
         }           
            catch (IOException e) {
                JOptionPane.showMessageDialog(this, "Beim einlesen ist ein Problem aufgetreten");
                 e.printStackTrace();
            }
        }


Über jede hilfreiche/Lehrreiche Antwort bedanke ich mich schon mal im vorraus.
 
Das Problem ist erst einmal, dass Du eben nicht über einen wahlfreien Zugriff auf das Wort zugreifen kannst.

Wie bei einer einfach verketten Liste musst Du von Anfang an durch die Datei durchgehen, um zu dem Wort zu kommen.

Das sind die Einschränkungen, die Du hast. Du kannst also nur über die Anzahl der Worte gehen um dann per Zufallszahl ein Wort auszuwählen. Und dann musst Du die Worte so lange einlesen, bis Du bei dem gewünschten Wort bist.

Wenn Du dies nicht willst, dann kannst Du es dir auch noch erweitern: Zu der Datei mit den Worten schreibst Du auch noch eine idx Datei. Immer wenn Du ein Wort schreibst, schreibst Du in diese Datei die Position, an die Du das Wort geschrieben hast.

Dann kannst über den Index gezielt den Startpunkt aus der idx Datei lesen und dann über diesen Startpunkt dann in der Wortdatei das Wort lesen.

(Das wäre doch eine super Übung, oder?)
 
Das Problem ist erst einmal, dass Du eben nicht über einen wahlfreien Zugriff auf das Wort zugreifen kannst.

Wie bei einer einfach verketten Liste musst Du von Anfang an durch die Datei durchgehen, um zu dem Wort zu kommen.

Das sind die Einschränkungen, die Du hast. Du kannst also nur über die Anzahl der Worte gehen um dann per Zufallszahl ein Wort auszuwählen. Und dann musst Du die Worte so lange einlesen, bis Du bei dem gewünschten Wort bist.

Wenn Du dies nicht willst, dann kannst Du es dir auch noch erweitern: Zu der Datei mit den Worten schreibst Du auch noch eine idx Datei. Immer wenn Du ein Wort schreibst, schreibst Du in diese Datei die Position, an die Du das Wort geschrieben hast.

Dann kannst über den Index gezielt den Startpunkt aus der idx Datei lesen und dann über diesen Startpunkt dann in der Wortdatei das Wort lesen.

(Das wäre doch eine super Übung, oder?)
Hallo KonradN,
vielen Dank für deine Nachricht.

Es ist also nicht möglich eine Variable als Zeiger zu benutzen, welche ein "Zufallsgenerator" übergeben bekommt?!
Im meinem Studienheft bearbeiten wir XML, txt, bin Dateien.
In dem Studienheft wird mit den Klassen FileWriter/-Reader und RandomAccessFile gearbeitet und ich glaube, dass es so gewollt ist das man
mit diesen Klassen arbeitet.
Andernfalls könntest du mir das anhand eines Bsp. erklären wie du das meinst mit ZITAT: Du kannst also nur über die Anzahl der Worte gehen um dann per Zufallszahl ein Wort auszuwählen. Und dann musst Du die Worte so lange einlesen, bis Du bei dem gewünschten Wort bist.

Die Klasse RandomAccessFile ist auch wirklich kompliziert -.-
 
Zuletzt bearbeitet:
Zumindest nicht sinnvoll. Denn du musst wirklich ein Wortanfang treffen.

Du hast z.B. 7 Worte und eine Dateilänge von 83 Bytes.

Du hast ja keine Information, an welchen Stellen die Worte anfangen. Nur beim ersten Wort kennst du den Anfang.

Wenn man die Worte nicht lesen will, dann könnte man auch nur die ersten zwei Byte als Zahl lesen. Das wäre die Länge des Strings. Dann könnte man einfach von Wort zu Wort springen ohne die Worte selbst zu lesen.

Vom Aufwand her macht das aber keinen Unterschied.
 
Wenn die Datensatzlänge nicht fix ist, kommst du um sequentielles Einlesen nicht rum.
Dabei ist ein gebufferter Zugriff schneller als viele einzelne kleine, bis du zum richtigen Ergebnis kommst. Ob du nun z.b. 20 Bytes einliest oder z.B. 4k ist beides genau gleich schnell, wenn das der Blockgröße der Partition entspricht.

Für das Zugreifen auf bestimmte Bytes innerhalb der Datei wird FileChannel anstatt RandomAccessFile empfohlen. RAF ist ziemlich langsam.
 
Dabei ist ein gebufferter Zugriff schneller als viele einzelne kleine, bis du zum richtigen Ergebnis kommst. Ob du nun z.b. 20 Bytes einliest oder z.B. 4k ist beides genau gleich schnell, wenn das der Blockgröße der Partition entspricht.
Wobei das System das schon übernehmen sollte. Daher ist das ein gutes Beispiel, dass man keine Optimierungen machen soll.

Das System liest Blöcke - das System kann also nicht nur 8 Bytes lesen sondern es wird direkt ein Block von der Platte gelesen und damit hast Du direkt 1, 2, 4, 8, ... K Daten. Bei meinem Raid würden es gleich 32K Daten sein, die das System bekommt.

Beware of Premature Optimization​



Warum?
Optimierungen kosten immer viel Aufwand. Wer Vorsicht walten lässt, spart oft wertvolle Ressourcen für das, was dem Kunden wirklich nützt.
 
Es ist also nicht möglich eine Variable als Zeiger zu benutzen, welche ein "Zufallsgenerator" übergeben bekommt?!
Doch, aber Du müsstest Dir dafür ein geeignetes Dateiformat überlegen.

Beispielsweise könntest Du die Wörter aufeinanderfolgend in die Datei schreiben, sagen wir mal Du bräuchtest dafür x Bytes, dann würde den Wörtern ein Verzeichnis folgen:

OffsetLängeInhalt
x40 (= Offset 1. Wort)
x+44Offset 2. Wort
...jeweils 4...
x+4*(n-1)4Offset n-tes Wort
x+4*n4x

Um damit auf das j-te Wort zuzugreifen, liest Du die letzten 4 Bytes der Datei ein, die den Offset (vom Dateianfang gesehen) auf den ersten Verzeichniseintrag enthalten. Der Offset entspricht x. Den musst Du nur einmal einlesen.

Anhand von x berechnest Du nun den Offset des Index-Eintrags, also x + 4*(j-1) und liest den Eintrag (4 Bytes) und den Folgeeintrag (4 Bytes) ein. Der erste der beiden Einträge enthält die Position des Worts in der Datei, die Differenz der beiden Einträge liefert die Wortlänge.

Das wäre ein erster Ansatz, der sich natürlich ausbauen lässt. Wenn Speicher eine untergeordnete Rolle spielt, kannst Du auch die Wortlänge vor jedes Wort schreiben (1 Byte), dann brauchst Du nur einen Eintrag aus dem Index zu lesen. Weiter könnte man die Datei in Seiten unterteilen, dann würde man komplette Seiten einlesen usw.
 
Bezüglich der Idee von @mihe7 nur ein kleiner Hinweis:
Die Methode, die den String in die Datei schreibst, schreibt bereits die Länge des Strings mit in die Datei (in 2 bytes).

Wenn also z.B. "aaa" mit writeUTF geschrieben wurde, dann wurde in die Datei geschrieben:
0x00 0x03 0x61 0x61 0x61

Die Wortlänge wird somit bereits vor jedes Wort geschrieben.
 

Zurück
Oben