Verkettete Liste in Datei

Hallo Leute.

Mal ne Frage zu folgendem Programm http://www.hh.schule.de/julius-leber-schule/UlfChris…

Auf dieser Liste habe ich mein Programm aufgebaut. Diese Liste schreibe ich in eine Datei. Das geht auch wunderbar. Aber wie kann ich das erste Element „Kopf“ in eine Datei schreiben? Diese wird ja als Klassenvaribale definiert und gleich an die Class Knoten übergeben. Bei den Folgeelmenten ist es ja kein Prob.

Wie könnte man’s anderes machen?

Danke
Rene

Hallo Rene.

Mal ne Frage zu folgendem Programm
http://www.hh.schule.de/julius-leber-schule/UlfChris…

Diese Liste schreibe ich in eine Datei. Das geht auch wunderbar.

Und wie machst Du das? Zeig mal den Code und schreib ihn der
besseren Lesbarkeit wegen zwischen pre-Tags.

Aber wie kann ich das erste Element „Kopf“ in eine Datei schreiben?

Vielleichst so wie Du den Rest speicherst?

Diese wird ja als Klassenvariable definiert

Da verwechselst Du Klassenvariablen mit Instanzvariablen.

Bei dem von Dir angesprochenen, nicht sonderlich schönen Code

class Kette 
{ 
 public Knoten kopf = new Knoten("kopf"); 
 public Knoten ende = new Knoten("ende"); 
 ...

handelt es sich um Instanzvariablen. Bei Klassenvariablen
muss das Schlüsselwort static dabei stehen.

und gleich an die Class Knoten übergeben.

Das ist leider auch nicht korrekt: Die Klasse Kette besitzt
zwei Knoten kopf und ende. Dabei wird kopf und ende nicht an
die Klasse Knoten übergeben, sie sind Knoten.

Technisch gesprochen erstellt die Klasse Kette zwei Instanzen
vom Typ bzw. der Klasse Knoten, die in den Instanzvariablen
kopf und ende gespeichert werden.

Ich empfehle Dir einen Blick in die kostenlose
Online-Java-Grundlagenliteratur aus [FAQ:2590] zum Thema
Objektorientierung.

Gruß,
-Andreas.

Servus…

hier mal meine Methode die das Schreiben in die Datei übernimmt. Das geht auch soweit. Aber wie ich das mit den Instanzvaribalen machen soll, ist mir ein Rätzel.

Code

public void fuegeNach(Object neuObj) throws FileNotFoundException, IOException 
 { 
 ByteArrayOutputStream ba = new ByteArrayOutputStream(); 
 ObjectOutputStream out = new ObjectOutputStream(ba);
 RandomAccessFile file = new RandomAccessFile("vFAT.txt", "rw");


 Knoten aktuellerKnoten = kopf;
 Knoten akt\_vorgaenger = kopf.vorgaenger;

 while (aktuellerKnoten != null) 
 {
 akt\_vorgaenger = aktuellerKnoten;
 aktuellerKnoten = aktuellerKnoten.naechster;
 }

 Knoten neuKnoten = new Knoten(neuObj);
 akt\_vorgaenger.naechster = neuKnoten;

 out.writeObject(neuKnoten);
 out.flush();
 out.close();

 byte[] newArray = ba.toByteArray(); // ByteStream in Array schreiben
 file.seek(pointer); // Pointer positionieren
 file.write(newArray); // schreibe Array in Datei
 pointer = file.getFilePointer(); // aktuelle Position in Datei merken
 ba.reset(); // ByteStream leeren
 file.close(); // Datei schließen
 } 

mfg
Rene

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hallo Rene.

hier mal meine Methode die das Schreiben in die Datei
übernimmt. Das geht auch soweit. Aber wie ich das mit den
Instanzvaribalen machen soll, ist mir ein Rätsel.

Du machst es etwas umständlich:

(a) Halte es einfach

Zum einen solltest Du dem Speichern und Laden eines Objekts immer eine extra Methode spendieren und diese Aufgaben nicht in die Methode zum Hinzufügen eines neuen Knotens mit hineinmischen.

Das hält den Code einfach und verständlich und folgt dem KISS-Prinzip: „Keep it simple stupid“ :wink:

(b) Nutze die Java-Speicherautomatik

Zum anderen übernimmt Java in den meisten Fällen das ganze Speichern und Laden der Objekte von selbst. Du musst da also nicht selbst Hand anlegen. Das einzige, um die Speicherautomatik von Java anzustossen, ist, dass jede zu speichernde Klasse die Schnittstelle java.io.Serializable implementieren muss.

Du fügst also jeder zu speichernden Klasse in der class-Zeile die 2 Wörtchen implements Serializable hinzu. Dann kannst Du sie per ObjectOutputStream.writeObject(deinSpeicherbaresObjekt) speichern und per ObjectInputStream.readObject wiederherstellen.

Damit es nicht zu abstrakt bleibt, hier der korrigierte Code:

import java.io.\*;

// Ein Knoten einer verketteten Liste
class Knoten implements Serializable {
 public Knoten naechstes = null; // zu Beginn hat ein Knoten keinen Nachfolger 
 public Object element = null; // und keinen Inhalt

 public Knoten(Object obj) {
 // neuen Inhalt zuweisen:
 element = obj; 
 }
}

// Eine verkettete Liste
public class Kette implements Serializable {
 public Knoten kopf = new Knoten("kopf");
 public Knoten ende = new Knoten("ende");

 public Kette() { 
 kopf.naechstes = ende;
 ende.naechstes = null;
 } 

 // fügt das gegebene Objekt als neuen Knoten hinter den Knoten vorgaenger ein
 public void fuegeNach(Knoten vorgaenger, Object neuObj) {
 if ( (vorgaenger == null) || (vorgaenger == ende) ) {
 throw new RuntimeException("Vorgänger darf nicht null oder das Ende der Liste sein");
 }
 // Objekt in neuen Knoten packen:
 Knoten neuKnoten = new Knoten(neuObj);
 // und diesen Knoten zwischen Vorgänger und dessen alten Nachfolger einfügen:
 // (a) der Nachfolger des Vorgängers wird Nachfolger des neuen Knotens
 neuKnoten.naechstes = vorgaenger.naechstes;

 // (b) der neue Knoten wird zum Nachfolger des Vorgängers
 vorgaenger.naechstes = neuKnoten; 
 }


 // entfernt den Nachfolger des Knotens vorgaenger aus der Liste
 public void entferneNach(Knoten vorgaenger) {
 if ( (vorgaenger == null) || (vorgaenger == ende) ) {
 throw new RuntimeException("Vorgänger darf nicht null oder das Ende der Liste sein");
 }

 Knoten loeschKnoten = vorgaenger.naechstes; 
 if (loeschKnoten == null) {
 throw new RuntimeException("Vorgänger hat keinen Nachfolger, der gelöscht werden kann");
 }

 // den zu löcshenden Knoten aus der Liste entfernen (=überspringen):
 vorgaenger.naechstes = loeschKnoten.naechstes;
 }

 // gibt die aktuelle Kette am Bildschirm aus
 public void ausgeben() {
 System.out.println(toString());
 }

 // gibt einen String mit dem aktuellen Objektinhalt zurück
 public String toString() {
 StringBuffer sb = new StringBuffer();

 // gebe den Hash-Code des Objekts mit aus, um die 
 // verschiedenen Instanzen voneinander unterscheiden 
 // zu können:
 sb.append("Kette@").append(Integer.toHexString(this.hashCode())).append("[\n");

 Knoten aktuellerKnoten = kopf;
 // Liste von Anfang bis Ende durchlaufen:
 for (int i=0; aktuellerKnoten!=null; i++) {
 sb.append("Knoten ").append(i).append(": ").append(aktuellerKnoten.element).append("\n");
 aktuellerKnoten = aktuellerKnoten.naechstes;
 } 
 sb.append("]");

 return sb.toString();
 }

 // speichert die aktuelle Kette in einer Datei
 public void speichern(String sDateiname) throws Exception {
 // siehe http://java.sun.com/j2se/1.5.0/docs/api/java/io/ObjectOutputStream.html
 FileOutputStream fos = new FileOutputStream (sDateiname);
 ObjectOutputStream oos = new ObjectOutputStream(fos);
 oos.writeObject(this);
 oos.close();
 }

 // lädt eine Kette aus einer mit writeObject gesicherten Datei
 public static Kette laden(String sDateiname) throws IOException, ClassNotFoundException {
 FileInputStream fis = new FileInputStream (sDateiname);
 ObjectInputStream ois = new ObjectInputStream (fis);
 Kette kette = (Kette)ois.readObject();
 ois.close();

 return kette;
 }

 // erzeugt eine verkette Liste, speichert sie in einer Datei und
 // lädt sie wieder aus der Datei
 public static void main(String[] params) throws Exception {
 Kette liste = new Kette();

 liste.fuegeNach (liste.kopf, "Nr. 3, reg' Dich nicht so auf sonst fliegst Du!");
 liste.fuegeNach (liste.kopf, "Ruhe da oben, ich muß mich konzentrieren!");
 liste.fuegeNach (liste.kopf, "Huhu Nr. 1, ich stehe gleich unter Dir...");
 liste.fuegeNach (liste.kopf, "Horridö, ich bin Element Nr. 1");

 System.out.println ("\*\*\* Speichere verkettete Liste");
 liste.ausgeben();

 String sDateiname = "kette.bin";
 liste.speichern(sDateiname);

 Kette wiederhergestellteListe = Kette.laden(sDateiname);
 System.out.println ("\*\*\* Wiederhergestellte verkettete Liste");
 wiederhergestellteListe.ausgeben();
 }
}

Gruß,
-Andreas.

Hallo Andreas…

Vielen dank, das hilft mir schon sehr weiter. Das mit dem Serialisieren der Kette wäre dann auch mein nächster Schritt gewesen.

Jetzt habe ich aber noch eine Frage. Wie funzt das, wenn ich dann später mal in den einzelnen Knoten Veränderungen vornehmen möchte? Sprich, ich will einzelne Zeichenketten durch andere ersetzen.

Muss ich dann immer die ganze Liste neu schreiben oder funktioniert das auch, wenn dann nur der eine veränderte Knoten geschrieben wird? Findet dann Java automatisch die alte Position in der Datei ohne weiteres wieder?

thx
Rene

Hallo Rene.

Wie funzt das, wenn ich
dann später mal in den einzelnen Knoten Veränderungen
vornehmen möchte? Sprich, ich will einzelne Zeichenketten
durch andere ersetzen.

Muss ich dann immer die ganze Liste neu schreiben oder
funktioniert das auch, wenn dann nur der eine veränderte
Knoten geschrieben wird?

Wenn Du die Liste serialisierst und Du danach einen Knoten
änderst, musst Du die gesamte Liste komplett neu speichern.

So lange die Liste nicht riesig ist, sollte das auch kein
Problem sein.

Gruß,
-Andreas.

So lange die Liste nicht riesig ist, sollte das auch kein
Problem sein.

Mist…sowas dachte ich mir schon. Von der Größe her kann die Liste von ca. 2MB bist vllt max 12MB anwachsen. Eine Möglichkeit wäre jetzt, dass ich die Liste immer beim Beenden des Programms erst wieder neu schreiben lasse oder ich ganz auf eine verkettete Liste verzichte und nur mit ArrayList - Objekten arbeite. Ursprünglich habe ich die ArrayList - Objekte in den einzelnen Knoten abgelegt. Die veränderten Objekte bräuchte ich ja dann nur wieder an die alte Pos. in der Datei setzten und gut. Nachteil wird aber das konfortable Durchsuchen, wie’s bei der Liste, wegfallen. Bin nämlich gerade am überlegen, wie ich die einzelnen ArrayList Objekte genauso nacheinander ansprechen kann, wie’s eigentlich die Liste gemacht habt.

gruss
Rene

Hallo Rene.

So lange die Liste nicht riesig ist, sollte das auch kein
Problem sein.

Mist…sowas dachte ich mir schon. Von der Größe her kann die
Liste von ca. 2MB bist vllt max 12MB anwachsen.

Eine Möglichkeit wäre jetzt, dass ich die Liste immer beim Beenden
des Programms erst wieder neu schreiben lasse

Genau so würde ich es machen. Die Serialisierung ist wegen dem
Festplattenzugriff immer eine sehr langsame Operation und sollte
deshalb nur äußerst sparsam verwendet werden.

Du hältst die Liste im laufenden Betrieb also im Hauptspeicher
und schreibst sie erst bei Programmende auf Platte.

Gruß,
-Andreas.

Oki…vielen Dank

Ein letzte Frage habe ich noch und zwar zu dem Schlüsselwort „this“, mit der du ja dann die ganze Liste schreibst.

„this“ sagt ja soviel aus, dass ich Zugriff über die Referenz auf das aktuelle Objekt habe (hoffe das stimmt soweit). Von dem Objekt Kette wird ja theor. nur ein Objekt angelegt, aber von Knoten werden viele Objekte angelegt. Ich kann mir noch nich so richtig selber erklären, warum dann die einzelenen Knoten in der Datei gespeichert werden.

Vllt in etwa so? „this“ zeigt ja auf das eigene Objekt und das Objekt Kette beinhaltet wieder viele Objekte Knoten. Weil Kette widerrum auf Objekte zeigt, die mit Kette verbunden sind, werden diese mit abgespeichert (this —> Kette —> Knoten) ??

Hallo nochmals.

Ein letzte Frage habe ich noch und zwar zu dem Schlüsselwort
„this“, mit der du ja dann die ganze Liste schreibst.

„this“ sagt ja soviel aus, dass ich Zugriff über die Referenz
auf das aktuelle Objekt habe (hoffe das stimmt soweit).

Jup, in dem Fall zeigt this auf die aktuelle Kette.

Von dem Objekt Kette wird ja theor. nur ein Objekt angelegt, aber
von Knoten werden viele Objekte angelegt. Ich kann mir noch
nich so richtig selber erklären, warum dann die einzelenen
Knoten in der Datei gespeichert werden.

Java kennt den internen Aufbau der Klassen und serialisiert alle
nicht-flüchtigen (d.h. nicht per volatile) markierten Attribute,
die es in der Klasse findet, wenn diese Serializable ist.

In unserem Fall sind dies die Attribute naechstes und element aus Knoten:

class Knoten implements Serializable {
 public Knoten naechstes = null;
 public Object element = null;
 ... 
}

sowie kopf und ende aus Kette:

public class Kette implements Serializable {
 public Knoten kopf = new Knoten("kopf");
 public Knoten ende = new Knoten("ende");
 ...
}

Wenn die Kette serialisiert wird, speichert Java zuerst die Knoten kopf und ende.
Darin befinden sich jeweils die Attribute naechstes und element. Da naechstes
ein Link auf einen weiteren Knoten der Kette ist, wird das Element auch
gleich mitserialisiert. Java folgt damit also automatisch allen Elementen der
Kette.

Gruß,
-Andreas.

Hallo Andreas…

Großes Problem…ich bekomme beim Abspeichner von mehr als 750 Objekten einen „StackOverflowError“. Was ist passiert? Kommt Java mit der Menge an Objekten nich klar, die er aufeinmal speichern muss?

Ich habe schonmal gehört, dass man den Stackspeicher von Java erhöhen kann, was aber auch keine dauerhafte Lösung ist.

Hast du vllt eine Idee, wie ich den Fehler umgehen kann?

Hier mal mein veränderter Code der main-Klasse

 public static void main(String[] params) throws Exception {
 String eing, anzahl, indexzahl, startbyte, endbyte, name, anz; 
 String nameeintrag = "$free";
 String nextcluster = "-1";
 int anzahlint, len\_i, len\_s, len\_e, i, anzint;
 int FileGroeße = 32;

 Kette liste = new Kette ();

 BufferedReader eingabe = new BufferedReader(new InputStreamReader(System.in));
 BufferedReader anzahlk = new BufferedReader(new InputStreamReader(System.in));

 try 
 {
 System.out.print("Anzahl der Kettenelemente? ");
 anzahl = anzahlk.readLine();
 anzahlint = Integer.parseInt(anzahl); 

 for (i=1; i

Danke
Rene

Hallo Rene.

Großes Problem…ich bekomme beim Abspeichner von mehr als
750 Objekten einen „StackOverflowError“. Was ist passiert?
Kommt Java mit der Menge an Objekten nich klar, die er
aufeinmal speichern muss?

Hast du vllt eine Idee, wie ich den Fehler umgehen kann?

Der StackOverflowError kommt offensichtlich daher, dass Java die Liste rekursiv speichert und sich beim Durchhangeln durch die Knoten der Liste einen immer größeren Kontext auf dem Stack merken muss.

Meine Idee:

(1) Halte Java davon ab, die Knoten rekursiv abzuspeichern
Dazu markierst Du in der Klasse Knoten den Zeiger auf das nächste Element per transient als nicht serialisierbar und lässt nur das Speichern des Elements zu:

class Knoten implements Serializable {
 public transient Knoten naechstes = null;
 public Object element = null;
 ... 
}

(2) Speichere die Listenstruktur manuell
Da nun der Zeiger auf das nächste Element nicht mehr gespeichert wird, musst Du selbst für das Speichern der Listenstruktur sorgen.

Dazu musst Du in der Klasse Kette zwei Methoden zur Verfügung stellen, writeObject und readObject, siehe http://java.sun.com/j2se/1.5.0/docs/api/java/io/Seri…

public class Kette implements Serializable {
 private void writeObject(java.io.ObjectOutputStream out)
 throws IOException {
 // durch alle Knoten iterieren und abspeichern
 }
 private void readObject(java.io.ObjectInputStream in)
 throws IOException, ClassNotFoundException {
 // alle Objekte nacheinander lesen, wieder in Knoten
 // zurückwandeln und in jedem Knoten den korrekten
 // Nachfolger setzen
 }
}

Die Umsetzung überlass ich heute mal Dir :wink:

Falls Deine Listen sehr gross werden können, ist übrigens der Einsatz einer Datenbank zum Speichern angeraten. Dann kannst Du auch einzelne Knoten selektiv hinzufügen und löschen, ohne dass die ganze Liste neu geschrieben werden muss und der Menge der Daten sind prinzipiell keine Grenzen mehr gesetzt.

Das Thema Persistenz ist übrigens ein weites Feld und es gibt eine Menge fertiger APIs, die das Problem angehen, z.B. JDO , siehe http://de.wikipedia.org/wiki/Java_Data_Objects.

Gruß,
-Andreas.

Hallo Andreas

habe hier mal meine Lösung. Das Problem ist, ich bekomme zwar die Elemente aus der Datei, aber ich weiss nicht wie ich diese mit der neuen Liste verbinden kann. Bei der Ausgabe der Liste wird mir quasi nur „kopf“ und „ende“ angezeigt.

Code speichern…Da ja „kopf“ und „ende“ schon in der Datei ist bzw. serialisiert wurde, habe ich es mal ausgeschlossen. Hoffe das stimmt soweit.

private void writeObject(java.io.ObjectOutputStream out) throws IOException 
 { 
 Knoten aktuellerKnoten = kopf;

 for (int i=0; aktuellerKnoten!=null; i++) 
 { 
 if (aktuellerKnoten != kopf || aktuellerKnoten != ende)
 { out.writeObject(aktuellerKnoten); }

 aktuellerKnoten = aktuellerKnoten.naechstes; 
 } 
 }

Code einlesen. Den habe ich erstma ohne Schleife gemacht. Habe vorher zwei Elemente in die Datei geschrieben plus „kopf“ und „ende“

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException 
 {
 Knoten knoten1 = (Knoten)in.readObject(); //kopf
 Knoten knoten2 = (Knoten)in.readObject(); //ArrayList
 Knoten knoten3 = (Knoten)in.readObject(); //ArrayList
 Knoten knoten4 = (Knoten)in.readObject(); //ende

 System.out.println(aktuellerKnoten.element);
 aktuellerKnoten.naechstes = knoten2;
 System.out.println(knoten2.element);
 knoten2.naechstes = knoten3;
 System.out.println(knoten3.element);
 knoten3.naechstes = knoten4;
 System.out.println(knoten4.element);
 knoten4.naechstes = null;
 }

Schau mal bitte drüber.

Danke

EDIT:

Ich bekomme jetzt Verbindung zum Rest der Kette durch den Befehl „static“ bei den Klassenvariablen. Erklären kann ich’s mir leider nicht! Außerdem habe ich noch bei Einlesen „kopf = knoten1“ gesetzt, damit dann die Verbindung zur Instanzvariable „kopf“ hergestellt wird. Das mit dem „static“ ist mir jedoch ein Rätzel.

public static Knoten kopf = new Knoten("kopf");
public static Knoten ende = new Knoten("ende");

gruss
Rene

Hallo Rene.

Ich bekomme jetzt Verbindung zum Rest der Kette durch den
Befehl „static“ bei den Klassenvariablen. Erklären kann ich’s
mir leider nicht! Außerdem habe ich noch bei Einlesen „kopf =
knoten1“ gesetzt, damit dann die Verbindung zur
Instanzvariable „kopf“ hergestellt wird. Das mit dem „static“
ist mir jedoch ein Rätzel.

public static Knoten kopf = new Knoten(„kopf“);
public static Knoten ende = new Knoten(„ende“);

Durch das static dürfte kopf und ende vom automatischen Serialisieren ausgeschlossen werden. Eigentlich wirst Du deshalb mit

public transient Knoten kopf = new Knoten("kopf");
public transient Knoten ende = new Knoten("ende");

und dem manuellen Speichern aller Knoten inklusive kopf und ende
zum Ziel kommen. Hab’s aber nicht ausprobiert, dass überlass
ich Dir :wink:

Gruß,
-Andreas.

Hallo Andreas…

Ich habe das jetzt alles soweit wie ich mir das vorgestellt habe hinbekommen. Jetzt wollte ich das Einlesen der Dateiinhalte in eine extra Klasse auslagern. Das funzt aber nur zum Teil.
Und zwar funzt das nur, wenn ich die zweite Funktion in der alten Klasse Kette belasse. Irgendwie findet er die nicht in der neuen Klasse. Woran könnte das denn liegen? Ich frage mich auch, wie die 2.te Methode überhaupt aufgerufen wird! Der Aufruf wurde ja nirgends ersichtlich definiert.

Danke
Rene

class einlesen extends Kette implements Serializable {

public static Kette laden() throws IOException, ClassNotFoundException
 { 
 FileInputStream fis = new FileInputStream ("DATEI.txt");
 ObjectInputStream ois = new ObjectInputStream (fis);

 Kette kette = (Kette)ois.readObject();
 ois.close();

 return kette;

 }

public void readObjectOverride(java.io.ObjectInputStream in)
 { \*\*\*\* }

}