# CSV-Datei auslesen, in Liste speichern und sortieren



## barodscheff (15. Mai 2014)

Hallo liebes Forum,

ich komme momentan bei einer Hausaufgabe nicht weiter und hoffe das mir hier jemand behilflich sein kann. Ich muss eine CSV-Datei mit beliebig vielen Zeilen und Spalten (Termine) auslesen, in einer verketteten Liste abspeichern und anschließend nach Datum sortiert ausgeben.

Hier mal die wichtigsten Fakten:






Was ich schon habe:

Termin.java

```
import java.io.File;

public class Termin {

    private String jahr, monat, tag, uhrzeit, ort, beschreibung;

    // Konstruktor
    Termin(String jahr, String monat, String tag, String uhrzeit, String ort, String beschreibung) {
	this.jahr = jahr;
	this.monat = monat;
	this.tag = tag;
	this.uhrzeit = uhrzeit;
	this.ort = ort;
	this.beschreibung = beschreibung;
    }

    // Überschreiben der toString-Methode
    @Override
    public String toString() {
	return String.format("%s. %s %s: %s Uhr\nOrt: %s\n%s", tag, monat,
		jahr, uhrzeit, ort, beschreibung);
    }

}
```

LinkedList.java

```
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.StringTokenizer;

public class LinkedList implements List {

    @Override
    public boolean isEmpty() {
	return false;
    }

    @Override
    public int length() {
	return 0;
    }

    @Override
    public Termin firstTermin() {
	return null;
    }

    @Override
    public List insert(Termin x) {
	return null;
    }

    @Override
    public List delete(Termin x) {
	return null;
    }

    // Einlesen der csv-Datei
    
    // Scanner init
    Scanner scan;
    
    // Konstruktor zum Einlesen
    LinkedList(File datei) {
	// try-catch überprüft ob Datei überhaupt vorhanden
	try {
	    scan = new Scanner(datei);
	} catch (FileNotFoundException e) {
	    e.printStackTrace();
	}
    }
    
    public Termin einlesen() {
	while(scan.hasNextLine()) {
	    String line = scan.nextLine();
	    StringTokenizer tokenizer = new StringTokenizer(line, ";");
	    String jahr = tokenizer.nextToken(),
		   monat = tokenizer.nextToken(),
		   tag = tokenizer.nextToken(),
		   uhrzeit = tokenizer.nextToken(),
		   ort = tokenizer.nextToken(),
		   beschreibung = tokenizer.nextToken();
	    return new Termin(jahr, monat, tag, uhrzeit, ort, beschreibung);
	}
	return null;
    }
    
    public void schliessen() {
	scan.close();
    }

}
```

Program.java

```
import java.io.File;


public class Programm {

    public static void main(String[] args) {
	File datei = new File("C://Users//Pawel//SkyDrive//3 - Uni//2. Semester//Programmieren 2//Übungen//test.csv");
	LinkedList linkedList = new LinkedList(datei);
	Termin termin = linkedList.einlesen();
	System.out.println(termin.toString());
	linkedList.schliessen();
    }

}
```

Die List.java erklärt sich von alleine, deshalb habe ich sie hier weggelassen.
Mein Problem ist momentan, dass das Einlesen der csv-Datei nicht funktioniert und folgender Fehler erscheint:

Exception in thread "main" java.util.NoSuchElementException
	at java.util.StringTokenizer.nextToken(Unknown Source)
	at LinkedList.einlesen(LinkedList.java:57)
	at Programm.main(Programm.java:9)

Wenn das funktioniert bin ich auch etwas ratlos wie ich das mit der Liste umsetze. Habt ihr Ideen oder Tipps?

LG


----------



## njans (16. Mai 2014)

Also erstmal: Das Einlesen von Files gehört von der Logik her nicht in die LinkedList. Die Liste selbst ist nur ein Speicher. Daher lese die Daten mal lieber in deiner main ein und erstelle dann die Termin Objekte und füge diese dann in die Liste ein.

Zum Zweiten: 
	
	
	
	





```
return new Termin(jahr, monat, tag, uhrzeit, ort, beschreibung);
```
 Danach springt dir deine Methode wieder zurück in die main. Du wirst so niemals mehr als eine Zeile deiner csv lesen.

Vermutlich ist in deiner Datei eine Leerzeile oder etwas ähnliches, weswegen dir die Tokens ausgehen. Generell würde ich dir aber auch zu einem deutlich einfacheren Ansatz raten: 
Scanner nimmt man generell eher weniger für das Parsen von Datein - Der BufferedReader ist da eher geeignet:


```
public MyLinkedList beispielLesenUndFuellen(String fileName)
	{
		MyLinkedList liste = new MyLinkedList();
		
		try (BufferedReader rd = new BufferedReader(new FileReader(fileName)))
		{
			for (String line = rd.readLine(); line != null; line = rd.readLine())
			{
                                // Evtl noch ifs mit bedingungen, um leere Zeilen auszugrenzen
				String [] attribute = line.split(";");
				Termin t = new Termin(attribute[0],attribute[1],attribute[2] ...) // USW
				liste.insert(e)
			}
		}
		catch (Exception e)
		{
			System.out.println("Fehler beim Einlesen der Datei!");
			e.printStackTrace();
		}
		
		return liste;
	}
```

Hiermit hättest du dann auch alles eingelesen und würdest es direkt in deine LinkedList Implementation einfügen.
=======================================

Was deine LinkedList Implementation angeht: Da solltest du zuerst bewusst sein, was eine LinkedList ist. Hierzu mal ein Beispiel aus Wikipedia:




Eine LinkedList hat Container, die sowohl Platz für ein Objekt (in deinem Fall Termin-objekt), als auch eine Referenz auf einen weiteren Container haben. Dabei hat die LinkedList immer eine Referenz auf das erste Element in der Liste (Die List wird hier als Kette von Containern realisiert). Jedes weitere Element kann man dadurch erreichen, dass man vom ersten, entlang der Referenzen zum nächsten Objekt geht und von der Referenz dann erneut weiter.


----------



## kaoZ (16. Mai 2014)

Kleine Frage am Rande , warum hier 


```
for (String line = rd.readLine(); line != null; line = rd.readLine()){...}
```

keine While Schleife ?


```
String line = "";
		
while((line = reader.readLine()) != null){
	//....
}
```

oder hatte das keinen bestimmten grund ? 

[EDIT]
sehe schon , du füllst in der Schleife ein String Array, mit welchem du den Termin erstellst, vergiss die Frage 
[/EDIT]


----------



## njans (16. Mai 2014)

Ich bevorzuge die Syntax der for-Schleife an dieser Stelle weil 
a) diese genau für solch einen iterativen Prozess gedacht ist 
b) die Aufteilung Initialisierung; Bedingung; Iterationsschritt recht übersichtlich bleibt 
c) ich inline Zuweisungen von der while Schleife stylistisch eher mau finde : while((line = reader.readLine()) != null)
d) ich dann gleich die line Variable im Kopf der Schleife definieren kann


----------



## barodscheff (16. Mai 2014)

Vielen Dank für die Antwort.
Ich verstehe jetzt was mein Fehler war.

Jedoch kriege ich es noch nicht ganz zusammen.

In Zeile 12 meintest du doch bestimmt


```
liste.insert(t)
```

oder? Sonst verstehe ich nicht wo das e herkommt.

Wenn ich es dann weiter richtig verstehe sorgt 


```
String [] attribute = line.split(";");
```

dafür, dass die einzelnen Elemente in einer Zeile durch das Semikolon getrennt werden und für jede neue Zeile ein neues Objekt Termin t angelegt und in der Liste gespeichert wird?!

Ich hab das Einlesen jetzt erst mal in der LinkedList.java gelassen, damit ich nicht komplett den Überblick verliere. Sobald ich das ausgeführt kriege wird es ausgelagert. 
Nun stehe ich etwas auf dem Schlauch und kriege es nicht in der main zum Laufen. Könntest du mir da auch auf die Sprünge helfen?

Momentan sieht es so aus, haut aber nicht hin.

```
import java.io.File;

public class Programm {

    public static void main(String[] args) {
	File datei = new File("C://Users//Pawel//SkyDrive//3 - Uni//2. Semester//Programmieren 2//Übungen//test.csv");
	LinkedList liste;
	Termin termin = liste.lesenUndFuellen(datei);
	System.out.println(termin.toString());
    }

}
```

Besten Dank im Voraus!


----------



## njans (16. Mai 2014)

Ja es müsste liste.insert(t) sein.



> dafür, dass die einzelnen Elemente in einer Zeile durch das Semikolon getrennt werden und für jede neue Zeile ein neues Objekt Termin t angelegt und in der Liste gespeichert wird?!



Ja, es trennt deinen String in Teilstrings an jedem ";" damit wird aus AA;BB;CC dann {AA,BB,CC}. Anschleißend packt man eben diese Daten in ein Termin-Objekt und gibt das deiner LinkedList.

Du solltest mal deine lesenUndFuellen Methode anpassen. Du hast bestimmt mehr als einen Termin in deiner csv ;-) 
Deshalb erstelle ich die LinkedList (in meinem Beispiel) auch innerhalb meiner Methode und gebe diese dann zurück. So kannst du dann einfach durch alle Zeilen der csv gehen und diese Zeile für Zeile in einen Termin umwandeln.


----------



## kaoZ (16. Mai 2014)

Man könnte das ganze ja auch mit einen Iteratator realisieren  

Und mit .hasNext (); abfragen ob weitere Zeilen vorhanden sind. 

Aber wie schon gesagt du hast schon recht rein logisch ist die for schleife an dieser Stelle leichter nachvollziehen und sinnvoller da du mit den einzelnen Indizes ja das Objekt initialisiert.


----------



## barodscheff (16. Mai 2014)

Ich habe mir nochmal die Aufgabe durchgelesen. Leider muss das Einlesen der csv-Datei in der LinkedList.java geschehen.

Hier der Auszug:





Ist echt das erste mal dass ich so eine schwere Aufgabe mache.


----------



## njans (16. Mai 2014)

hmm dann würde ich wohl sowas machen: (Dein geposteter Source Code)

```
import java.io.File;
import java.io.FileNotFoundException;

public class MyLinkedList implements List {

    @Override
    public boolean isEmpty() {
	return false;
    }

    @Override
    public int length() {
	return 0;
    }

    @Override
    public Termin firstTermin() {
	return null;
    }

    @Override
    public List insert(Termin x) {
	return null;
    }

    @Override
    public List delete(Termin x) {
	return null;
    }
        
    public static MyLinkedList vonDatei(File datei)
    {
        MyLinkedList liste = new MyLinkedList();
       
        try (BufferedReader rd = new BufferedReader(new FileReader(fileName)))
        {
            for (String line = rd.readLine(); line != null; line = rd.readLine())
            {
                // Evtl noch ifs mit bedingungen, um leere Zeilen auszugrenzen
                String [] attribute = line.split(";");
                Termin t = new Termin(attribute[0],attribute[1],attribute[2] ...) // USW
                liste.insert(e)
            }
        }
        catch (Exception e)
        {
            System.out.println("Fehler beim Einlesen der Datei!");
            e.printStackTrace();
        }
        return liste;
    }
}
```

Dann kannst du einfach MyLinkedList.vonDatei(<deine csv datei>) machen und erhälst eine gefüllte Liste.
Ich nehme an, dass es dann bei dir an der Implementation der LinkedList hapert, daher empfehle ich dir mal den Wikipedia Eintrag "Verkettete Liste" durchzulesen und dann mal zu probieren das umzusetzen. Wenn du dann nicht weiter weißt, dann stelle deine Fragen einfach und wir können dann helfen.


----------



## barodscheff (16. Mai 2014)

Ok, ich schaue mal.

Aber eine andere Frage habe ich dennoch:
Warum schreibst du:


```
try (BufferedReader rd = new BufferedReader(new FileReader(fileName)))
        {
            for (String line = rd.readLine(); line != null; line = rd.readLine())
            {
                                // Evtl noch ifs mit bedingungen, um leere Zeilen auszugrenzen
                String [] attribute = line.split(";");
                Termin t = new Termin(attribute[0],attribute[1],attribute[2] ...) // USW
                liste.insert(e)
            }
        }
```

und nicht


```
try { 
            BufferedReader rd = new BufferedReader(new FileReader(fileName));
```

Ich dachte so wäre die Syntax von try/catch.


----------



## njans (16. Mai 2014)

```
try (BufferedReader rd = new BufferedReader(new FileReader(fileName)))
```
nennt sich "try with resources" und wurde in Java 7 eingefürhrt. Oftmals kann beim Lesen oder schreiben etwas schiefgehen und dann fliegt eine exception. Problem ist dann, dass man den Reader/writer wieder schließen will aber auch da kann eine Exception fliegen. Da du am Ende dann mehrere try-catch Blöcke ineinander schachteln müsstest um alle Situationen abzufangen und den reader/writer wieder zu schließen, wurde dieses Konstrukt erschaffen, so dass das schließen und der exception-strom sauber gehandhabt werden kann.


----------



## barodscheff (16. Mai 2014)

Ich habe mir jetzt den Wikipedia Beitrag und auch einige weitere Beiträge / Videos angeschaut.
Ich habe das Gefühl mein Verständnis von Listen in Java ist schlechter geworden. Die Listen an sich verstehe ich ja, aber über die Umsetzung in Java sagt jeder was anderes.

Es geht jetzt nur noch um die beiden letzten Schritte: Das Einfügen in der Liste und das Ausgeben der Termin-Objekte (das sortierte Ausgeben versuche ich erst gar nicht).

Warum funktioniert


```
liste.insert(t)
```

in meiner LinkedList.java steht doch nur return null in der insert-Methode.

In der main-Methode sieht mein Code jetzt so aus:


```
public class Programm {

    public static void main(String[] args) {
	String dateiName = "test.csv";
	LinkedList liste = new LinkedList();
	System.out.println(liste.einlesenUndFuellen(dateiName));
    }

}
```

Wenn ich das ausführe erhalte ich :

Fehler beim Einlesen der Datei!
java.lang.ArrayIndexOutOfBoundsException: 5
null
	at LinkedList.einlesenUndFuellen(LinkedList.java:38)
	at Programm.main(Programm.java:7)

In der Aufgabenstellung war es auch gefodert, dass ich die toString-Methode für die Ausgabe verwende. Würde es funktionieren, dass ich in der main-Methode eine for-Schleife erstelle und mit jedem Durchlauf das i-te Termin-Objekt der Liste mittels .toString ausgebe?

In etwa so, wobei die Termin-Klasse die toString-Methode besitzt.


```
System.out.println(liste.einlesenUndFuellen(dateiName));
	for(int i = 0; i<liste.length; i++) {
	    System.out.println(liste[i].toString);
	}
```

Fragen über Fragen... ???:L


----------



## njans (16. Mai 2014)

```
in meiner LinkedList.java steht doch nur return null in der insert-Methode.
```
Und das ist auch eine valide Methode da sie syntaktisch korrekt ist. Semantisch macht diese nichts, außer null zurückgeben, aber das ist eben die Sache des Programmieres.



> at LinkedList.einlesenUndFuellen(LinkedList.java:38)



Was steht denn in einlesenUndFuellen in deiner LinkedList.java in Zeile 37- 39 ?

Du kannst jetzt schon sehen, dass du dort ein Array hast, dass du versuchst an Stelle 5 zuzugreifen, das Array aber kleiner ist.



> In der Aufgabenstellung war es auch gefodert, dass ich die toString-Methode für die Ausgabe verwende. Würde es funktionieren, dass ich in der main-Methode eine for-Schleife erstelle und mit jedem Durchlauf das i-te Termin-Objekt der Liste mittels .toString ausgebe?



Dein Beispiel ist schon richtig. Du kannst entweder toString() direkt an einem Objekt aufrufen oder es weglassen: System.out.println(liste_); geht auch, da jedes Objekt eine toString Methode implementiert und java dann diese Methode implizit aufruft._


----------



## barodscheff (16. Mai 2014)

```
String[] attribute = line.split(";");
		Termin t = new Termin(attribute[0], attribute[1], attribute[2], attribute[3], attribute[4], attribute[5]);
		liste.insert(t);
```


----------



## njans (16. Mai 2014)

Gib doch mal line mit einem sysout aus. Ich würde mal sagen, da ist entweder ne leere Zeile drin oder du hast da einen Syntax Fehler, du kannst ja mal die Datei hier reinkopieren.


----------



## barodscheff (16. Mai 2014)

In der csv sind keine Leerzeilen. Hier mal ein Bild:






Und hier noch der gesamte Code:

List.java (Interface)

```
public interface List {
    
    boolean isEmpty(); // true, wenn die Liste keine Elemente enthält
    
    int length(); // Länge der Liste
    
    Termin firstTermin(); // erster zeitlicher Termin
    
    List insert(Termin x); // den Termin x zeitlich sortiert einfügen
    
    List delete(Termin x); // den Termin x entfernen
    
}
```

LinkedList.java

```
import java.io.*;

public class LinkedList implements List {

    @Override
    public boolean isEmpty() {
	return false;
    }

    @Override
    public int length() {
	return 0;
    }

    @Override
    public Termin firstTermin() {
	return null;
    }

    @Override
    public List insert(Termin x) {
	return null;
    }

    @Override
    public List delete(Termin x) {
	return null;
    }

    // Einlesen der csv-Datei

    public LinkedList einlesenUndFuellen(String dateiName) {
	LinkedList liste = new LinkedList();

	try (BufferedReader rd = new BufferedReader(new FileReader(dateiName))) {
	    for (String line = rd.readLine(); line != null; line = rd.readLine()) {
		String[] attribute = line.split(";");
		Termin t = new Termin(attribute[0], attribute[1], attribute[2], attribute[3], attribute[4], attribute[5]);
		liste.insert(t);
	    }
	    return liste;
	} catch (Exception e) {
	    System.out.println("Fehler beim Einlesen der Datei!");
	    e.printStackTrace();
	    return null;
	}
    }

}
```

Termin.java

```
public class Termin {

    private String jahr, monat, tag, uhrzeit, ort, beschreibung;

    // Konstruktor
    Termin(String jahr, String monat, String tag, String uhrzeit, String ort, String beschreibung) {
	this.jahr = jahr;
	this.monat = monat;
	this.tag = tag;
	this.uhrzeit = uhrzeit;
	this.ort = ort;
	this.beschreibung = beschreibung;
    }

    // Überschreiben der toString-Methode
    @Override
    public String toString() {
	return String.format("%s. %s %s: %s Uhr\nOrt: %s\n%s", tag, monat,
		jahr, uhrzeit, ort, beschreibung);
    }

}
```

main-Methode: Programm.java

```
public class Programm {

    public static void main(String[] args) {
	String dateiName = "test.csv";
	LinkedList liste = new LinkedList();
	System.out.println(liste.einlesenUndFuellen(dateiName));
    }

}
```


Ich habe seperat eine Test-Klasse geschrieben die die Datei einlist und im gewünschten Format ausgibt. Funktioniert ohne andere Klassen und Listen einwandfrei. Es muss also an der Liste liegen.


----------



## njans (16. Mai 2014)

4te Zeile in deiner csv Datei, da fehlt das letzte ";".
Dann sollte das auch alles durchlaufen. Du musst du nur noch die LinkedList implementieren.


----------



## barodscheff (16. Mai 2014)

Ja, wenn du mir da noch hilfst dann bist du mein Held der Woche


----------



## njans (16. Mai 2014)

Was ist denn dein Ansatz? Du sagtest ja, du hättest bereits dich etwas schlau gemacht.


----------



## barodscheff (16. Mai 2014)

Ich habe eine alte ähnliche Aufgabe mit Lösung gefunden und konnte das auf mein Problem anwenden bzw. abändern. Jetzt funktioniert es zumindest unsortiert.

Meine LinkedList sieht so aus:


```
import java.io.*;

public class LinkedList implements List {

    private Termin item;
    private LinkedList next;
    
    // Initialisierung von item und next
    public LinkedList() {
	item = null;
	next = null;
    }
    
    // Getter für item und next
    
    public Termin getItem() {
	return item;
    }

    public LinkedList getNext() {
	return next;
    }
    
    // Überschreiben der Methoden vom Interface

    public boolean isEmpty() {
	return next == null;
    }
    
    public int length() {
	if (isEmpty())
	    return 0;
	else
	    return 1 + next.length();
    }

    public Termin firstTermin() {
	if (isEmpty())
	    return null;
	else
	    return next.item;
    }

    public LinkedList insert(Termin x) {
	if (isEmpty()) {
	    LinkedList l = new LinkedList();
	    l.item = x;
	    l.next = next;
	    next = l;
	    return this;
	} else
	    return getNext().insert(x);
    }
    
    public LinkedList delete(Termin x) {
	LinkedList l = find(x);
	if (l != null)
	    l.next = l.next.next;
	return this;
    }
    
    // Wird für delete benötigt
    private LinkedList find(Termin x) {
	if (isEmpty())
	    return null;
	else if (firstTermin().equals(x))
	    return this;
	else
	    return next.find(x);
    }

    public String toString() {
	return (next == null ? "" : "" + next.item + "\n" + next);
    }
    

    // Einlesen der csv-Datei

    public LinkedList einlesenUndFuellen(String dateiName) {
	LinkedList liste = new LinkedList();

	try (BufferedReader rd = new BufferedReader(new FileReader(dateiName))) {
	    for (String line = rd.readLine(); line != null; line = rd
		    .readLine()) {
		String[] attribute = line.split(";");
		Termin t = new Termin(attribute[0], attribute[1], attribute[2],
			attribute[3], attribute[4], attribute[5]);
		liste.insert(t);
	    }
	    return liste;
	} catch (Exception e) {
	    System.out.println("Fehler beim Einlesen der Datei!");
	    e.printStackTrace();
	    return null;
	}
    }

}
```

Eine Kleinig dabei kann ich aber nicht nachvollziehen:


```
public String toString() {
	return (next == null ? "" : "" + next.item + "\n" + next);
    }
```

Leider verstehe ich nicht wieso erst damit die to.String-Methode funktioniert. Handelt es sich bei dem Fragezeichen um einen Bedingungsoperator? Was passiert da?

Das Sortieren wollte ich zwar erst weglassen, doch jetzt überlege ich es doch umzusetzen. Suche da nach einem Ansatz.


----------



## njans (16. Mai 2014)

Also diese Implementation ist ziemlich hässlich da sie sich selbst als Container verwendet.

Vorweg: 

```
// Getter für item und next
   
    public Termin getItem() {
    return item;
    }
 
    public LinkedList getNext() {
    return next;
    }
```
Die Methoden sollten private sein, nicht public. Von außen soll keine andere Klasse diese Methoden rufen können, da sie über die interne Implementation preis geben.


```
public String toString() {
        return (next == null ? "" : "" + next.item + "\n" + next);
        }
```

Der ternäre Operator da ist eine bedingte Zuweisung  (bedingung)?wenn_ja:wenn_nein;
Generell verwendet deine Implementation das Kettenprinzip und jedes Element ruft das nächste auf. Genauso bei toString. 
Wenn die liste nicht leer ist, dann gibt er das item aus und anschließend ruft er toString von dem nächsten Container auf. 
Dieser gibt dann wieder das nächste Element aus und das geht dann so lange weiter bis man ende (ein Element hat keinen Nachfolger) ankommt.


----------

