Dateiverarbeitung - Wortliste abspeichern & Wörter zählen

Einen wunderschönen Guten Tag wünsche ich jedem Lesenden und im allgemeinen der Java-Gemeinde.

Ich hänge aktuell an einer Aufgabe fest die mir mehr Schwierigkeiten bereitet als gedacht...

Ich soll einen Editor erstellen, mit dem ich eine Wortliste in einer Datei ablegen kann.
Jedoch sollen nicht nur die Wörter an sich abgespeichert werden, sondern auch die Anzahl der gespeicherten Wörter...

Ich bekomme es schon mal hin das die Wörter in die Datei geschrieben und auch wieder raus gegeben werden.
Das Zählen der Wörter bereitet aktuell noch Probleme.. ich würde die Zahl gerne an den Anfang der Datei schreiben und dann
immer wieder aktualisieren.. also die Zahl aus der 1. Zeile quasi löschen und ersetzen..

hat jemand eine Idee ?? vielleicht ist der Lösungsansatz ja komplett falsch und ich muss irgendwas mit der Random access class machen..?

der von mir erstellte Quelltext sieht bislang so aus..

Java:
package aufgabe1;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Texteditor extends JFrame {
    //automatisch über Eclipse ergänzt
    private static final long serialVersionUID = 6468516705291496250L;
    //für das Eingabefeld
    private JTextArea feld;
    //für die Schaltflächen
    private JButton einlesen, beenden, schreiben;
    
//
    private int anzahlWoerter = 0;
//
    
    //die innere Klasse für den ActionListener
    class MeinListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            //wurde auf Lesen geklickt?
            if (e.getActionCommand().equals("lesen"))
                //dann die Datei einlesen
                dateiLesen();
            //wurde auf Schreiben geklickt?
            if (e.getActionCommand().equals("schreiben"))
                //dann die Datei schreiben
                dateiSchreiben();
            //wurde auf Beenden geklickt?
            if (e.getActionCommand().equals("ende"))
                System.exit(0);
        }
    }

    //der Konstruktor
    public Texteditor(String titel) {
        super(titel);
        //für das Panel mit den Schaltflächen
        JPanel tempPanel;
        //ein neues Eingabefeld erstellen
        feld = new JTextArea();
        //die Schaltflächen
        einlesen = new JButton("Einlesen");
        einlesen.setActionCommand("lesen");
        schreiben = new JButton("Schreiben");
        schreiben.setActionCommand("schreiben");
        beenden = new JButton("Beenden");
        beenden.setActionCommand("ende");
        
        MeinListener listener = new MeinListener();
        einlesen.addActionListener(listener);
        schreiben.addActionListener(listener);
        beenden.addActionListener(listener);
                
        //ein BorderLayout anwenden
        setLayout(new BorderLayout());
        //das Eingabefeld mit Scrollbars
        add(new JScrollPane(feld), BorderLayout.CENTER);
        //ein Panel für die Schaltflächen
        tempPanel = new JPanel();
        tempPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        tempPanel.add(einlesen);
        tempPanel.add(schreiben);
        tempPanel.add(beenden);
        add(tempPanel,BorderLayout.SOUTH);
        
        //Größe setzen, Standard-Verhalten festlegen und anzeigen
        setMinimumSize(new Dimension(400,300));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    //die Methode zum Lesen
    private void dateiLesen() {
        //eine Instanz der Klasse FileReader mit der Datei daten.txt
        try (FileReader datei = new FileReader("Listendaten.txt")){
            //in das Textfeld lesen
            feld.read(datei, null);
        }
        catch (IOException e ) {
            JOptionPane.showMessageDialog(this, "Beim Laden ist ein Problem aufgetreten");
        }
    }

    //die Methode zum Schreiben
    private void dateiSchreiben() {
        //eine Instanz der Klasse FileWriter mit der Datei daten.txt
        //true im Konstruktor setzen, damit ein neues Wort ganz einfach hinzugefügt werden kann,
        //ohne das vorherige Wort zu überschreiben
        try (FileWriter datei = new FileWriter("Listendaten.txt", true)){
            //die Anzahl der Worte in die 1. Zeile der Datei schreiben
            datei.append(String.valueOf(anzahlWoerter)).append("\n");
            
            //den Inhalt ausdem Textfeld + einen Zeilensprung
            //in die Datei "Listendaten.txt" einfügen
            datei.append(feld.getText()).append("\n");
            
            //die Methode zum Hochzählen der Worte aufrufen
            woerterZaehlen();
        }
        catch (IOException e ) {
            JOptionPane.showMessageDialog(this, "Beim Schreiben ist ein Problem aufgetreten");
        }
    }

    private int woerterZaehlen() {
        anzahlWoerter++;
        return anzahlWoerter;
    }
}
 

KonradN

Super-Moderator
Mitarbeiter
Also erst einmal ein allgemeiner Hinweis zu funktionaler Programmierung speziell sogenannten Lambdas: Du kannst eine Art anonyme Methode weitergeben. Dabei hast Du erst die Parameter ohne Typen, ein "Pfeil" und dann Code, der den Aufruf macht.

Wenn ein Interface nur die Implementation einer Methode fordert, dann nennt man das funktionales Interface und wie Du siehst: ActionListener hat nur genau eine Methode, die implementiert werden muss.

Wenn Du nun ein funktionales Interface angeben musst, dann kannst Du an der Stelle ein Lambda Ausdruck angeben. In Deinem Fall ist es eine Methode mit einem Parameter: ActionEvent e. Somit kann ein Lambda so aussehen: e -> dateiLesen().

Das kannst Du jetzt umsetzen in deinem Code:
a) Die innere Klasse entfällt komplett. Dieser Code wird also gelöscht:
Java:
    class MeinListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            //wurde auf Lesen geklickt?
            if (e.getActionCommand().equals("lesen"))
                //dann die Datei einlesen
                dateiLesen();
            //wurde auf Schreiben geklickt?
            if (e.getActionCommand().equals("schreiben"))
                //dann die Datei schreiben
                dateiSchreiben();
            //wurde auf Beenden geklickt?
            if (e.getActionCommand().equals("ende"))
                System.exit(0);
        }
    }

b) Den Block, wo du dies nutzt, verändert sich nun. Aus:
Java:
        MeinListener listener = new MeinListener();
        einlesen.addActionListener(listener);
        schreiben.addActionListener(listener);
        beenden.addActionListener(listener);

wird nun einfach:
Java:
        einlesen.addActionListener( e -> dateiLesen() );
        schreiben.addActionListener( e -> dateiSchreiben() );
        beenden.addActionListener( e -> System.exit(0) );

Das macht den Code dann deutlich lesbarer (kürzer und Du hast bei der Zuweisung direkt die jeweiligen Aufrufe, d.h. es ist direkt ersichtlich, was da aufgerufen wird).

Bezüglich der eigentlichen Problematik schreibe ich direkt im Anschluss etwas.
 

KonradN

Super-Moderator
Mitarbeiter
Um in eine Datei erst die Anzahl zu schreiben und dann die Worte, kannst Du prinzipiell so machen, wie bisher. Wenn Du in der ersten Zeile aber die Anzahl hast, dann musst du das natürlich noch erweitern:
Java:
        try (FileReader datei = new FileReader("Listendaten.txt")){
            // TODO: Hier muss erst die erste Zeile gelesen werden

            //in das Textfeld lesen
            feld.read(datei, null);
        }

Jetzt ist nur die Frage: Wie liest man am einfachsten die erste Zeile?

a) FileReader - der kann das natürlich. Einfach per read Zeichen für Zeichen lesen, alles zusammen bauen und dann bei dem Newline Aufhören zu lesen. Die Zeichen müssen dann natürlich zusammengebaut werden zu dem eigentlichen Wert um diesen dann in ein int Umzuwandeln.

b) Es gibt natürlich Reader, die das schon alles können. Hier kommt dann der BufferedReader ins Spiel. Gebaut wird dieser einfach mit einem verschachtelten Aufruf - der gebaute FileReader wird im Konstruktor übergeben:
Java:
try (BufferedReader datei = new BufferedReader(new FileReader("Listendaten.txt")){
Das lesen einer Zeile ist dann einfach ein readLine() Aufruf.
 
erst einmal danke ich dir für die Mühe..
aber ich bin ehrlich zu dir.. ich verstehe das alles nicht so ganz.
Damit meine ich wo ich was einsetzen muss und so weiter..

Dazu kommt ja noch das Problem dass ich die zwischennummern nicht mit lesen möchte..
 

Jw456

Top Contributor
Für mich brauchst du die Methode zum hochzälen nicht. Dein JTextArea biedet das doch schon. Schaue doch mal in die Doku.
GetColums() git dir doch die Anzahl der Wörter in dem Array zurück.

Wenn du bei schreiben zuerst die Anzahl speicherst und dann das Array.

Dann musst du natürlich auch beim lesen erst Die Anzahl zurück lesen. Und dann das Array.


 
Zuletzt bearbeitet:

Jw456

Top Contributor
Für mich machst du das mit dem schreiben auch etwas falsch. Du hängst ja immer an die vorhandene Datei an. Für mich wäre sinnvoller nicht anzuhängen sondern neu zuschreiben.
Somit würde in der Datei immer an erster Stelle die Anzahl stehen. Und dann die Wörter.
 

Robert Zenz

Top Contributor
In meinen Beispielen habe ich zwei solche: Swing-Wortliste-RandomAccessFile und Swing-Wortliste. Die Beispiele sind gut dokumentiert, und helfen vielleicht dabei die ein oder andere Vorgehensweise zu demonstrieren und zu verstehen auf die man selber nicht kommt.

Zusaetzlich zu dem was die anderen gesagt haben, noch ein paar kleine Hinweise.

Java:
        catch (IOException e ) {
            JOptionPane.showMessageDialog(this, "Beim Schreiben ist ein Problem aufgetreten");
        }

Das ist nicht hilfreich, du solltest auch Informationen ueber den Fehler ausgeben, oder zumindest auf die Konsole drucken. Ansonsten erhaeltst du jetzt waehrend dem entwickeln einfach nur "ging nicht" und musst erst raten was los war. Das mindeste waere:

Java:
        catch (IOException e ) {
            e.printStackTrace();
           
            JOptionPane.showMessageDialog(this, "Beim Schreiben ist ein Problem aufgetreten");
        }

Der Methodenname von woerterZaehlen ist auch falsch, das zaehlt keine Woerter, es erhoeht den Zaehler der Woerter, das ist ein wichtiger Unterschied. Auf die Namen und aehnliches zu achten ist immer wichtig.

Die serialVersionUID kannst du auch streichen. Eclipse (und andere?) schlagen sie zwar vor, aber brauchen tust du es nur dann wenn du den Java-Serialisierungs-Mechanismus verwenden willst, was man eigentlich nie so richtig will.

Java:
    private JButton einlesen, beenden, schreiben;

Traditionell versucht man mehrere Deklarationen in der gleichen Zeile zu vermeiden, da es den Code meistens schwerer zu lesen macht. Waehrend man ueber Ungarische Notation streiten kann, finde ich einen "gut lesbaren" Ansatz fuer Variablennamen auf jeden Fall sinnvoll. Also zum Beispiel anstelle von schreiben hier, waere das dateiSchreibenButton, dann weisz man immer was in der Variable drinnen ist und wofuer sie da ist.
 

KonradN

Super-Moderator
Mitarbeiter
Ich habe einmal etwas Deinen Code verändert, damit Du die Vorschläge einmal umgesetzt sehen kannst.

Java:
package aufgabe1;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Texteditor extends JFrame {
    //automatisch über Eclipse ergänzt
    private static final long serialVersionUID = 6468516705291496250L;
    //für das Eingabefeld
    private JTextArea feld;
    //für die Schaltflächen
    private JButton einlesen, beenden, schreiben;

    private int anzahlWoerter = 0;

    //der Konstruktor
    public Texteditor(String titel) {
        super(titel);
        //für das Panel mit den Schaltflächen
        JPanel tempPanel;
        //ein neues Eingabefeld erstellen
        feld = new JTextArea();
        //die Schaltflächen
        einlesen = new JButton("Einlesen");
        einlesen.addActionListener( e -> dateiLesen() );
        schreiben = new JButton("Schreiben");
        schreiben.addActionListener(e -> dateiSchreiben() );
        beenden = new JButton("Beenden");
        beenden.addActionListener( e -> System.exit(0) );


        //ein BorderLayout anwenden
        setLayout(new BorderLayout());
        //das Eingabefeld mit Scrollbars
        add(new JScrollPane(feld), BorderLayout.CENTER);
        //ein Panel für die Schaltflächen
        tempPanel = new JPanel();
        tempPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        tempPanel.add(einlesen);
        tempPanel.add(schreiben);
        tempPanel.add(beenden);
        add(tempPanel,BorderLayout.SOUTH);

        //Größe setzen, Standard-Verhalten festlegen und anzeigen
        setMinimumSize(new Dimension(400,300));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    //die Methode zum Lesen
    private void dateiLesen() {
        //eine Instanz der Klasse FileReader mit der Datei daten.txt
        try (BufferedReader datei = new BufferedReader(new FileReader("Listendaten.txt"))) {
            // anzahlWoerter lesen
            anzahlWoerter = Integer.parseInt(datei.readLine());

            //in das Textfeld lesen
            feld.read(datei, null);
        }
        catch (IOException e ) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Beim Laden ist ein Problem aufgetreten: " + e.getMessage());
        }
    }

    //die Methode zum Schreiben
    private void dateiSchreiben() {
        //eine Instanz der Klasse FileWriter mit der Datei daten.txt
        //true im Konstruktor setzen, damit ein neues Wort ganz einfach hinzugefügt werden kann,
        //ohne das vorherige Wort zu überschreiben
        anzahlWoerter = feld.getLineCount();
        try (FileWriter datei = new FileWriter("Listendaten.txt", false)){
            //die Anzahl der Worte in die 1. Zeile der Datei schreiben
            datei.append(String.valueOf(anzahlWoerter)).append("\n");

            //den Inhalt ausdem Textfeld + einen Zeilensprung
            //in die Datei "Listendaten.txt" einfügen
            datei.append(feld.getText());
        }
        catch (IOException e ) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Beim Schreiben ist ein Problem aufgetreten: " + e.getMessage());
        }
    }
}

Änderungen sind dabei:

1. Ich habe keine innere Klasse mehr. Statt dessen habe ich - wie vorgeschlagen - Lambda Expressions verwendet damit die Methoden aufgerufen werden. Zusätzlich habe ich keine Action Command Werte mehr gesetzt, denn die werden einfach nicht gebraucht.

2. Ich habe das Speichern angepasst: Ich gehe davon aus, dass pro Zeile ein "Wort" gewollt ist. Da anzahlWoerter ja nicht aktuell ist, wird erst ausgelesen, wie viele Zeilen in dem Control sind. Dann wird erst die diese Zahl geschrieben und dann im Anschluss der Inhalt des Feldes. Wichtig: Da wird kein Zeilenumbruch mehr angehängt! Beim Laden hast Du da sonst eine leere Zeile und das wäre dann sozusagen das leere Wort ... Und wie richtig gesagt wurde: wir hängen da nichts mehr an sondern schreiben die Datei immer neu.

3. Das Laden wurde auch angepasst: Es wird erst die erste Zeile gelesen und der Wert in der Variable gespeichert. Dann wird der Rest direkt in den Eingabebereich gesetzt.

Damit hast Du dann einmal, wie die Umsetzung der Vorschläge aussehen würde.
 

Blender3D

Top Contributor
Ich bekomme es schon mal hin das die Wörter in die Datei geschrieben und auch wieder raus gegeben werden.
Das Zählen der Wörter bereitet aktuell noch Probleme.. ich würde die Zahl gerne an den Anfang der Datei schreiben und dann
immer wieder aktualisieren.. also die Zahl aus der 1. Zeile quasi löschen und ersetzen..

hat jemand eine Idee ?? vielleicht ist der Lösungsansatz ja komplett falsch und ich muss irgendwas mit der Random access class machen..?
Also falls ich die Aufgabe richtig verstehe, willst du einen Editor erstellen der es ermöglicht Wörter einer Liste einzugeben. Pro Zeile steht also ein Wort. Diese Liste kann dann in eine Datei geschrieben oder geladen werden. Diese Datei beinhaltet außerdem die Anzahl der enthalten Wörter.
Für die Eingabe verwendest du ein JTextArea. Du benutzt außerdem die Methode read() dieser Klasse, um die Datei zu lesen. Das ist schon einmal ein grundlegendes Problem da deine Schreiben Methode in der Ersten Zeile die Anzahl der Wörter speichert. Diese Zahl wird dadurch gelesen und der Benutzter kann sie jederzeit im Editor sehen und ändern. Diese Zahl sollte also nicht angezeigt werden. Außerdem ermöglicht der Editor dem Benutzer auch mehrere Wörter in eine Zeile zu schreiben.
Das führt dazu die Lesemethode und die Schreibmethode zu überdenken.
Ein möglicher Ansatz wäre, vor dem Schreiben den Text so zu ändern, dass pro Zeile immer nur ein Wort steht und Lehrzeilen entfernt werden.
(Wenn gewünscht könnte man auch doppelte Wörter aus der Liste entfernen.)
Beim Lesen kann man dann die erste Zeile einlesen die die Anzahl der Wörter enthält gefolgt von der Wortliste, die man anschließend in das Textelement setzt. Ich würde außerdem die Datei nicht anhängen sondern immer wieder neu schreiben.
 

Neue Themen


Oben