1. Dateien und wahlfreier Zugriff auf Dateiinhalte
Auch wenn vieles in die Cloud und Datenbank wandert: Das Dateisystem ist immer noch ein wichtiger Speicher und Ort zur Organisation von Dokumenten. Auch Bonny Brain und Captain CiaoCiao legen noch vieles lokal ab — es gibt genug, was nicht in die Öffentlichkeit geraten darf.
Voraussetzungen
File
-Klasse,Path
-Schnittstelle undFiles
-Klasse in den Grundzügen kennentemporäre Dateien anlegen können
Metadateien von Dateien und Verzeichnissen erfragen können
Verzeichnisinhalte auflisten und filtern können
vollständige Dateien lesen und schreiben können
RandomAccessFile
kennen
Verwendete Datentypen in diesem Kapitel:
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹
1.1. Path und Files
Wie bei so vielen Sachen in Java gibt es auch bei der Dateiverarbeitung den »alten« und den »neuen« Weg. In vielen Beispielen sieht man noch immer Code mit den Typen java.io.File
, FileInputStream
, FileOutputStream
, FileReader
und FileWriter
, doch diese Typen sind nicht mehr zeitgemäß, weshalb wir uns in diesem Kapitel ausschließlich mit Path
und Files
befassen wollen, denn diese Typen ermöglichen den Einsatz von virtuellen Dateisystemen, wie einem ZIP-Archiv. File
ist nur noch dann zwingend, wenn es tatsächlich um Dateien oder Verzeichnisse des lokalen Dateisystems geht; Beispiele wären das Öffnen von Dateien mit dem vom Betriebssystem assoziierten Programmen oder das Umlenken von Datenströmen von extern gestarteten Programmen.
1.1.1. Spruch des Tages anzeigen ⭐
Hin und wieder kann sich Captain CiaoCiao nicht so richtig motivieren. Ein Motivations- oder Sinnspruch für den Tag bringt den Murrkopf auf neue Gedanken. Es soll eine Anwendung programmiert werden, die eine HTML-Datei mit einem Spruch erzeugt und dann den Browser öffnet, um diesen Text anzuzeigen. Die Aufgabe lässt sich mit zwei Methoden von java.nio.files.Files
lösen.
Aufgabe:
Lege mit einer passenden
Files
-Methode eine temporäre Datei an, die mit dem Datei-Suffix.html
endet.Schreibe in die neue temporäre Datei HTML-Code, etwa den folgenden:
<!DOCTYPE html><html><body> ›Die Dinge, die wir stehlen, sagen uns, wer wir sind.‹ - Thomas von Tew </body></html>
Suche aus der Klasse
java.awt.Desktop
eine Methode, die den Standard-Browser öffnet und somit die HTML-Datei anzeigt.
1.1.2. Verstecke zusammenführen ⭐
Mit gewissen Files
-Methoden lässt sich in einem Rutsch eine ganze Datei zeilenweise einlesen und wieder schreiben.
Captain CiaoCiao sammelt in einer großen Textdatei potenzielle Verstecke. Doch oft fallen ihm spontan weitere Verstecke ein, und er schreibt sie schnell in eine neue Datei. Nun nimmt er sich die Zeit und räumt auf und fasst alles zusammen; die kleinen Textdateien sollen mit der großen Datei zusammengelegt werden. Wichtig ist, dass die Reihenfolge der Einträge der großen Datei nicht geändert wird und nur die Einträge aus den kleinen Dateien aufgenommen werden, wenn sie nicht in der großen Datei vorkommen, denn es kann sein, dass in der Hauptdatei schon längst die Verstecke stehen.
Aufgabe:
Schreibe eine Methode
mergeFiles(Path main, Path... temp)
, die die Master-Datei öffnet, alle temporären Inhalte ergänzt, und dann die Master-Datei zurückschreibt.
1.1.3. Kopien einer Datei erstellen ⭐⭐
Wenn man zum Beispiel im Windows-Explorer eine Datei in den gleichen Ordner kopiert, wird eine Kopie angelegt. Diese Kopie bekommt automatisch einen neuen Namen. Gesucht ist ein Java-Programm, das dieses Verhalten nachbildet.
Aufgabe:
Schreibe eine Java-Methode
cloneFile(Path path)
, die Kopien von Dateien erzeugt und dabei die Dateinamen systematisch generiert. Nehmen wir an,<Name>
symbolisiert den Dateinamen, dann wird die erste KopieCopy of <Name>
heißen und anschließend sollen die DateinamenCopy (<Zahl>) of <Name>
lauten.Ruft man die Methoden auf Verzeichnissen auf oder gibt es andere Fehler, kann die Methode eine
IOException
auslösen.
Beispiel:
Nehmen wir an, eine Datei heißt Top Secret UFO Files.txt. Dann sollen die neuen Dateinamen so aussehen:
Copy of Top Secret UFO Files.txt
Copy (2) of von Top Secret UFO Files.txt
Copy (3) of von Top Secret UFO Files.txt
usw.
1.1.4. Verzeichnislisting generieren ⭐
Auf der Kommandozeile kann sich der Benutzer die Verzeichnisinhalte und Metadaten anzeigen lassen, genauso wie auch ein Dateiauswahldialog dem Benutzer Dateien anzeigt.
Aufgabe:
Schreibe mit
Files
und der MethodenewDirectoryStream(…)
ein Programm, das den Verzeichnisinhalt für das aktuelle Verzeichnis auflistet.Rufe unter DOS das Programm
dir
auf. Bilde die Ausgabe des Verzeichnislistings vollständig nach. Der Kopf und Fuß sind nicht nötig.
1.1.5. Nach einer großen GIF-Datei suchen ⭐
Auf der Festplatte von Bonny Brain herrscht Chaos, auch weil sie alle Bilder in genau einem Verzeichnis speichert. Nun sind die Bilder von der letzten Schatzsuche unauffindbar! Sie weiß nur noch, dass die Bilder im GIF-Format gespeichert waren und sie über 1024 Pixel breit waren.
Aufgabe:
Gegeben ist ein beliebiges Verzeichnis. Suche in diesem Verzeichnis (nicht rekursiv!) nach allen Bildern, die vom Typ GIF sind und eine Mindestbreite von 1024 Pixeln aufweisen.
Greife zum Auslesen der Breiten und zur GIF-Prüfung auf folgenden Code zurück:
private static final byte[] GIF87aGIF89a = "GIF87aGIF89a".getBytes();
private static boolean isGifAndWidthGreaterThan1024( Path entry ) {
if ( ! Files.isRegularFile( entry ) || ! Files.isReadable( entry ) )
return false;
try ( RandomAccessFile raf = new RandomAccessFile( entry.toFile(), "r" ) ) {
byte[] bytes = new byte[ 8 ];
raf.read( bytes );
if ( ! Arrays.equals( bytes, 0, 6, GIF87aGIF89a, 0, 6 ) &&
! Arrays.equals( bytes, 0, 6, GIF87aGIF89a, 6, 12 ) )
return false;
int width = bytes[ 6 ] + (bytes[ 7 ] << 8);
return width > 1024;
}
catch ( IOException e ) {
throw new UncheckedIOException( e );
}
}
Die Methode liest die ersten Bytes ein und schaut nach, ob die ersten 6 Bytes entweder dem String GIF87a
oder GIF89a
entsprechen. Prinzipiell lässt sich dieser Test auch mit ! new String(bytes, 0, 6).matches("GIF87a|GIF89a")
umsetzen, doch das würde einiges an temporären Objekten im Speicher nach sich ziehen.
Nach der Prüfung liest das Programm 2 Bytes für die Breite aus und konvertiert die Bytes in eine 16-Bit-Ganzzahl.
1.1.6. Verzeichnisse rekursiv absteigen und leere Textdateien finden ⭐
Auf der Festplatte von Bonny Brain ist immer noch ein großes Durcheinander. Aus unerklärlichen Gründen hat sie viele Textdateien mit 0 Byte.
Aufgabe:
Laufe mit einem
FileVisitor
rekursiv ab einem gewählten Startverzeichnis alle Unterverzeichnisse ab, und suche nach leeren Textdateien.Textdateien sind Dateien, die die Dateiendung .txt tragen (unabhängig von der Groß-/Kleinschreibung).
Bei einem Fund zeige den absoluten Pfad der Datei auf der Konsole.
1.1.7. Eigene Utility-Bibliothek für Dateifilter entwickeln ⭐⭐⭐
Die Files
-Klasse bietet drei statische Methoden zum Erfragen aller Einträge in einem Verzeichnis:
newDirectoryStream(Path dir)
newDirectoryStream(Path dir, String glob)
newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter)
Das Ergebnis ist immer ein DirectoryStream<Path>
. Die erste Methode filtert die Ergebnisse nicht, die zweite Methode erlaubt einen Glob-String wie etwa *.txt
, und die dritte Methode erlaubt einen beliebigen Filter.
java.nio.file.DirectoryStream.Filter<T>
ist eine Schnittstelle, die von Filtern implementiert werden muss. Die Methode lautet boolean accept(T entry)
und ist wie ein Prädikat.
Die Java-Bibliothek deklariert zwar die Schnittstelle aber keine Implementierung.
Aufgabe:
Schreibe diverse Implementierungen von
DirectoryStream.Filter
, die Dateien prüfen können aufAttribute (wie lesbar, schreibbar)
die Länge
die Dateiendungen
den Dateinamen über reguläre Ausdrücke
magische Anfangskennungen
Im Idealfall erlaubt die API eine Verkettung aller Filter, etwa so:
DirectoryStream.Filter<Path> filter =
regularFile.and( readable )
.and( largerThan( 100_000 ) )
.and( magicNumber( 0x89, 'P', 'N', 'G' ) )
.and( globMatches( "*.png" ) )
.and( regexContains( "[-]" ) );
try ( DirectoryStream<Path> entries =Files.newDirectoryStream( dir, filter ) ) {
entries.forEach( System.out::println );
}
1.2. Wahlfreier Zugriff auf Dateiinhalte
Für Dateien lässt sich ein Ein-/Ausgabestrom besorgen und von vorne bis hinten auslesen oder schreiben. Eine andere API erlaubt den wahlfreien Zugriff, also einen Positionszeiger.
1.2.1. Datenbanken mit fixen Datensätze verwalten ⭐⭐
Wenn man sich anschaut wie Datenbankmanagementsysteme die Daten physikalisch ablegen, wird man immer wieder auf Dateien mit fester Satzlänge kommen, engl. fixed-length format. Dabei bestehen die Dateien aus Blöcken fester Länge, die ohne Trennung sequenziell hintereinander liegen. Die Einheiten nennt man Records.
Beispiel: Jedes Record ist 6 Symbole lang:
AAAAAABBBBBBCCCCCCDDDDDDAAAAAA
Bei so einem Format lässt sich leicht zu einem Datensatz springen — man multipliziert den gewünschten Datensatz mit der Länge, hier 6, und positioniert den Zeiger und liest 6 Zeichen aus.
Aufgabe:
Schreibe eine Klasse
RecordDatabase
, die Operationen anbietet zum Lesen, Anhängen und Aktualisieren von Records. Überlege, wie man Records löschen könnte.Das Dateiformat ist wie folgt:
In den ersten vier Bytes steht eine Kennung,
CCDB
(CiaoCiao-Database)Es folgen 6 Bytes mit einem Datumsstempel, wann die Datenbank zuletzt verändert wurde, das Format ist
YYMMDD
.Es folgen 4 Byte (ein
int
) mit der Anzahl Records in der Datenbank.Es folgen 4 Byte (ein
int
) mit der Breite jedes Records; die Breite ist in Byte.Es folgen die Records.
1.2.2. Letzte Zeile einer Textdatei ausgeben ⭐⭐
Die Crew-Mitglieder schreiben alle Aktionen in ein elektronisches Logbuch, wobei die neuen Einträge hinten angehängt werden. Kein Eintrag ist länger als 100 Zeichen, die Texte sind in UTF-8 geschrieben.
Nun interessiert sich Captain CiaoCiao für den letzten Eintrag. Wie sieht ein Java-Programm aus, wenn aus einer Datei nur die letzte Zeile ausgelesen werden soll? Da schon sehr viele Einträge im Logbuch stehen, ist es nicht möglich, die Datei komplett einzulesen.
Aufgabe:
Schreibe ein Programm, das die letzte Zeile einer Textdatei liefert.
Finde eine Lösung, die nicht unnötig viel Speicher benötigt.
Überlege, ob sich |
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹