Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil19 Einführung in Dateien und Datenströme
Pfeil19.1 Alte und neue Welt in java.io und java.nio
Pfeil19.1.1 java.io-Paket mit File-Klasse
Pfeil19.1.2 NIO.2 und das java.nio-Paket
Pfeil19.1.3 java.io.File oder java.nio.*-Typen?
Pfeil19.2 Dateisysteme und Pfade
Pfeil19.2.1 FileSystem und Path
Pfeil19.2.2 Die Utility-Klasse Files
Pfeil19.3 Dateien mit wahlfreiem Zugriff
Pfeil19.3.1 Ein RandomAccessFile zum Lesen und Schreiben öffnen
Pfeil19.3.2 Aus dem RandomAccessFile lesen
Pfeil19.3.3 Schreiben mit RandomAccessFile
Pfeil19.3.4 Die Länge des RandomAccessFile
Pfeil19.3.5 Hin und her in der Datei
Pfeil19.4 Basisklassen für die Ein-/Ausgabe
Pfeil19.4.1 Die vier abstrakten Basisklassen
Pfeil19.4.2 Die abstrakte Basisklasse OutputStream
Pfeil19.4.3 Die abstrakte Basisklasse InputStream
Pfeil19.4.4 Die abstrakte Basisklasse Writer
Pfeil19.4.5 Die Schnittstelle Appendable *
Pfeil19.4.6 Die abstrakte Basisklasse Reader
Pfeil19.4.7 Die Schnittstellen Closeable, AutoCloseable und Flushable
Pfeil19.5 Lesen aus Dateien und Schreiben in Dateien
Pfeil19.5.1 Byteorientierte Datenströme über Files beziehen
Pfeil19.5.2 Zeichenorientierte Datenströme über Files beziehen
Pfeil19.5.3 Die Funktion von OpenOption bei den Files.newXXX(…)-Methoden
Pfeil19.5.4 Ressourcen aus dem Modulpfad und aus JAR-Dateien laden
Pfeil19.6 Zum Weiterlesen
 

Zum Seitenanfang

19.2    Dateisysteme und Pfade Zur vorigen ÜberschriftZur nächsten Überschrift

Im Zentrum von NIO.2 stehen die Typen FileSystem und Path:

  • FileSystem beschreibt ein Datensystem und ist eine abstrakte Klasse. Es wird von konkreten Dateisystemen, wie dem lokalen Dateisystem oder einem ZIP-Archiv, realisiert. Um an das aktuelle Dateisystem zu kommen, deklariert die Klasse FileSystems eine statische Methode: FileSystems.getDefault().

  • Path repräsentiert einen Pfad zu einer Datei oder einem Verzeichnis, wobei die Pfadangaben relativ oder absolut sein können. Die Methoden erinnern ein wenig an die alte Klasse File, doch der große Unterschied ist, dass File selbst die Datei oder das Verzeichnis repräsentiert und Abfragemethoden wie isDirectory() oder lastModified() deklariert, während Path nur den Pfad repräsentiert und nur pfadbezogene Methoden anbietet. Modifikationsmethoden gehören nicht dazu; dazu dienen Extra-Typen wie BasicFileAttributes für Attribute.

 

Zum Seitenanfang

19.2.1    FileSystem und Path Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Path-Objekt lässt sich nicht wie File über einen Konstruktor aufbauen, da die Klasse abstrakt ist. File und Path haben aber dennoch einiges gemeinsam, etwa dass sie immutable sind. Das FileSystem-Objekt bietet die entsprechende Methode getPath(…), und ein FileSystem wird über eine Fabrikmethode von FileSystems erfragt.

[zB]  Beispiel

Baue ein Path-Objekt auf:

FileSystem fs = FileSystems.getDefault();

Path p = fs.getPath( "C:/Windows/Fonts/" );

Mit einer Abkürzung:

Path p = Paths.get( "C:/Windows/Fonts/" );

Da der Ausdruck FileSystems.getDefault().getPath(…) etwas unhandlich ist, gibt es drei kürzere Wege:

  • Es gibt die Utility-Klasse Paths, und ein Aufruf von Paths.get(…) liefert den Path (siehe Zwei Freunde müsst ihr werden – bidirektionale 1:1-Beziehungen).

  • Seit Java 11 gibt es in Path zwei statische of(…)-Methoden.

  • Auch aus einem File-Objekt lässt sich mit toPath() ein Path ableiten.

Wir werden die Vereinfachung mit Paths.get(…) im Folgenden nutzen. Die Path.of(…)-Variante ist noch etwas neu, und noch benutzt nicht jeder Java 11.

final class java.nio.file.Paths
  • static Path get(String first, String... more)

    Erzeuge einen Pfad aus Segmenten. Wenn etwa der Backslash \ der Separator ist, dann ist Paths.get("a", "b", "c") gleich Paths.get("a\\b\\c").

  • static Path get(URI uri)

    Erzeugt einen Pfad aus einem URI.

final class java.nio.file.Path
  • static Path of(String first, String... more) – Seit Java 11

  • static Path of(URI uri) – Seit Java 11

Jedes Path-Objekt hat auch eine Methode getFileSystem(), mit der wir wieder an das FileSystem kommen.

Abhängigkeiten der Typen »Paths« und »Path«

Abbildung 19.1    Abhängigkeiten der Typen »Paths« und »Path«

[»]  Hinweis

Der Pfad-String darf unter Java kein \u0000 enthalten, andernfalls gibt es eine Ausnahme. So führt Paths.get("my.php\u0000.jpg") zur Ausnahme »java.nio.file.InvalidPathException: Illegal char < > at index 6: my.php«. Das ist ein wichtiges Sicherheitsmerkmal, um zum Beispiel einen Webserver davor zu schützen, falsche Dateien anzunehmen. Der Dateiname sieht über "my.php\u0000.jpg".endsWith(".jpg") wie eine JPG-Datei aus, aber würde alles nach dem Null-String abgeschnitten (was einige Dateisysteme machen), wäre plötzlich eine Datei my.php angelegt.

Path-Eigenschaften erfragen

Der Path-Typ deklariert diverse getXXX(…)-Methoden (und eine isAbsolute()-Methode), die eine gewisse Ähnlichkeit mit den Methoden aus File haben (siehe Abbildung 19.2). Listing 19.1 zeigt ein paar Anwendungsbeispiele:

Listing 19.1    src/main/java/com/tutego/insel/nio2/FileSystemPathFileDemo1.java, main()

Path p = Paths.get( "C:/Windows/Fonts/" );

System.out.println( p.toString() ); // C:\Windows\Fonts

System.out.println( p.isAbsolute() ); // true

System.out.println( p.getRoot() ); // C:\

System.out.println( p.getParent() ); // Fonts

System.out.println( p.getNameCount() ); // 2

System.out.println( p.getName(p.getNameCount()-1) ); // Fonts

Methoden wie getPath(), getRoot() und getParent() liefern alle wiederum Path-Objekte aus den Bestandteilen eines gegebenen Pfades. Es gibt drei Methoden, um das Ergebnis nicht als Path weiterzuverarbeiten:

  • toString() liefert eine String-Repräsentation,

  • die Methode toUri() liefert einen URI und

  • toFile() liefert ein traditionelles File-Objekt.

Dadurch, dass Path eine hierarchische Liste von Namen für den Pfad speichert, lässt sich jedes Segment des Pfades erfragen. Das ist die Aufgabe von getName(int n), das wiederum einen Path liefert. Die Methode subpath(int beginIndex, int endIndex) liefert einen Path mit den Segmenten des angegebenen Bereichs. Path implementiert die Iterable-Schnittstelle, was eine Methode iterator() vorschreibt – das wiederum bedeutet, dass Path rechts vom Doppelpunkt im erweiterten for auftauchen kann.

Das Klassendiagramm von »Path«

Abbildung 19.2    Das Klassendiagramm von »Path«

Praktisch sind die Prüfmethoden startsWith(Path other) und endsWith(Path other), die feststellen, ob der Pfad mit einem bestimmten anderen Pfad beginnt oder endet. Aus Object wird equals(…) überschrieben. Da Path die Schnittstelle Comparable<Path> realisiert, wird zudem compareTo(Path) implementiert. Die Methode equals(…) löst die Pfade nicht auf, sondern betrachtet nur den Namen; die statische Methode isSameFile(Path, Path) der Klasse Files macht diesen Test und löst relative Bezüge auf. Neben equals(…) überschreibt Path auch hashCode().

interface java.nio.file.Path

extends Comparable<Path>, Iterable<Path>, Watchable
  • String toString()

  • File toFile()

  • URI toUri()

  • Path getFileName()

  • Path getParent()

  • Path getRoot()

  • boolean isAbsolute()

  • int getNameCount()

  • Path getName(int index)

  • Iterator<Path> iterator()

  • Path subpath(int beginIndex, int endIndex)

  • boolean endsWith(Path other)

  • boolean endsWith(String other)

  • boolean startsWith(Path other)

  • boolean startsWith(String other)

  • boolean equals(Object other)

  • int compareTo(Path other)

  • int hashCode()

Die Vererbungsbeziehung von »Path«

Abbildung 19.3    Die Vererbungsbeziehung von »Path«

[»]  Hinweis

Die Methode getFileName() liefert keinen String, sondern ein Path-Objekt nur mit dem Dateinamen, bei dem also getNameCount() == 1 ist.

Daher führt path.getFileName().endsWith(".xml") zum Testen, ob ein Dateiname wie trainings.xml auf ».xml« endet, nicht zum Ziel, denn endsWith(…) testet, ob das letzte Segment im Pfad (in diesem Fall der komplette Dateiname, nur ohne Verzeichnisangabe) exakt diesen Namen trägt. Als Lösung ist zum Beispiel path.getFileName().toString().endsWith(".xml") gültig.

[»]  Hinweis

Unterschiedliche Dateisysteme haben ihre ganz eigenen Beschränkungen und Schreibweisen. Mal ist das Ordnertrennerzeichen ein »/«, mal ein »\«. Eine Wikipedia-Seite[ 264 ](https://en.wikipedia.org/wiki/Path_(computing)#Representations_of_paths_by_operating_system_and_shell) gibt einen Überblick, wie unterschiedliche Betriebssysteme Separatoren oder das Wurzelverzeichnis definieren. Java akzeptiert unter Windows auch den Slash als Ordnertrenner.

Neue Pfade aufbauen

Die resolveXXX(…)-Methoden bauen aus gegebenen Pfaden neue Pfade zusammen. Die Methoden akzeptieren die Parametertypen String und Path.

[zB]  Beispiel

Hänge das Benutzerverzeichnis mit dem Bilderverzeichnis zusammen:

Path picturePath = Paths.get( System.getProperty("user.home") )

.resolve( "Pictures" )

.resolve( "Cora" );

System.out.println( picturePath ); // z. B. C:\Users\Chris\Pictures\Cora

Die Unterverzeichnisse lassen sich sonst auch direkt in get(…) eintragen.

Im gleichen Ordner wie stormyDaniels.jpg ist ein neuer Pfad für die Datei pee_tape.tar gefragt:

Path stormy = Paths.get( "d:/geheimakten/usa/stormyDaniels.jpg" );

Path peepee = stormy.resolveSibling( "pee_tape.tar" );

System.out.println( peepee ); // d:\geheimakten\usa\pee_tape.tar

Eine interessante Methode ist auch relativize(Path) – sie liefert aus einer Basisangabe einen relativen Pfad, der zu einem anderen Pfad führt.

[zB]  Beispiel

Von c:/Windows/Fonts nach c:/Windows/Cursors führt der relative Pfad ..\Cursors:

System.out.println( Paths.get( "C:/Windows/Fonts" )

.relativize( Paths.get("C:/Windows/Cursors") )

); // ..\Cursors
interface java.nio.file.Path

extends Comparable<Path>, Iterable<Path>, Watchable
  • Path relativize(Path other)

  • Path resolve(Path other)

  • Path resolve(String other)

  • Path resolveSibling(Path other)

  • Path resolveSibling(String other)

Normalisierung und Pfadauflösung

Genauso wie die File-Klasse symbolisiert die Path-Klasse einen Pfad, aber dieser muss nicht auf eine konkrete Datei oder ein konkretes Verzeichnis zeigen. Daher liefern die vorgestellten Methoden lediglich Informationen, die sich aus dem vorgegebenen Namen erschließen lassen, ohne auf das Dateisystem zurückzugreifen. Bei relativen Pfaden liefern die Anfragemethoden daher wenig Spannendes:

Listing 19.2    src/main/java/com/tutego/insel/nio2/FileSystemPathFileDemo2.java, main()

Path p = Paths.get( "../.." );

System.out.println( p.toString() ); // ..\..

System.out.println( p.isAbsolute() ); // false

System.out.println( p.getRoot() ); // null

System.out.println( p.getParent() ); // ..

System.out.println( p.getNameCount() ); // 2

System.out.println( p.getName(p.getNameCount()-1) ); // ..

Um ein wenig Ordnung in relative Pfadangaben zu bringen, bietet die Path-Klasse die Methode normalize(), die ohne Zugriff auf das Dateisystem die Bezüge ».« und »..« entfernt.

Zum Auflösen der relativen Adressierung mit Zugriff auf das Dateisystem bietet die Path-Klasse die beiden Methoden toAbsolutePath() bzw. toRealPath(…) an:

Listing 19.3    src/main/java/com/tutego/insel/nio2/RealAndAbsolutePath.java, main()

Path p2 = Paths.get( "../.." );

System.out.println( p2.toAbsolutePath() );

// C:\Users\Christian\Documents\Insel\programme\2_06_Files\..\..

try {

System.out.println( p2.toRealPath( LinkOption.NOFOLLOW_LINKS ) );

// C:\Users\Christian\Documents\Insel

}

catch ( IOException e ) { e.printStackTrace(); }

Die erste Methode toAbsolutePath() normalisiert nicht, sondern löst einfach nur den relativen Pfad in einen absoluten Pfad auf. Die Auflösung vom ../.. erledigt toRealPath(LinkOption...), wobei das (optionale) Argument ausdrückt, ob Verknüpfungen verfolgt werden sollen oder nicht.

[»]  Hinweis

Die Methode toRealPath(…) löst eine Ausnahme aus, wenn versucht wird, einen Pfad zu einer Datei aufzulösen, der nicht existiert. So führt zum Beispiel Paths.get("../0x").toRealPath() zur »java.nio.file.NoSuchFileException: C:\Users\Chris\0x«, egal ob mit LinkOption.NOFOLLOW_LINKS oder ohne. Paths.get( "../0x" ).toAbsolutePath() führt zu keinem Fehler.

interface java.nio.file.Path

extends Comparable<Path>, Iterable<Path>, Watchable
  • Path normalize()

  • Path toAbsolutePath()

  • Path toRealPath(LinkOption... options)

Es gibt noch zwei register(…)-Methoden in Path, doch die haben etwas mit dem Anmelden eines Horchers zu tun, der auf Änderungen im Dateisystem reagiert. Sie werden später vorgestellt.

 

Zum Seitenanfang

19.2.2    Die Utility-Klasse Files Zur vorigen ÜberschriftZur nächsten Überschrift

Da die Klasse Path nur Pfade, aber keine Dateiinformationen wie die Länge oder Änderungszeit repräsentiert, und da Path auch keine Möglichkeit bietet, Dateien anzulegen und zu löschen, übernimmt die Klasse Files diese Aufgaben.

Eine einfache Methode ist size(…), die die Länge der Datei liefert. Anders als bei java.io.File führen nahezu alle Files-Methoden zu einer IOException, wenn es Probleme bei den Ein-/Ausgabe-Operationen gibt.

final class java.nio.file.Files
  • static long size(Path path) throws IOException

    Liefert die Größe der Datei.

Einfaches Einlesen und Schreiben von Dateien

Mit den Methoden readAllBytes(…), readAllLines(…), readString(…), lines(…), write(…) und writeString(..) kann Files einfach einen Dateiinhalt einlesen oder Strings bzw. ein Byte-Feld schreiben.

Listing 19.4    src/main/java/com/tutego/insel/nio2/ListAllLines.java, main()

URI uri = ListAllLines.class.getResource( "/lyrics.txt" ).toURI();

Path p = Paths.get( uri );

System.out.printf( "Datei '%s' mit Länge %d Byte(s) hat folgende Zeilen:%n",

p.getFileName(), Files.size( p ) );

int lineCnt = 1;

for ( String line : Files.readAllLines( p ) )

System.out.println( lineCnt++ + ": " + line );
final class java.nio.file.Files
  • static byte[] readAllBytes(Path path) throws IOException

    Liest die Datei komplett in ein Byte-Feld ein.

  • static List<String> readAllLines(Path path) throws IOException

  • static List<String> readAllLines(Path path, Charset cs) throws IOException

    Lesen die Datei Zeile für Zeile ein und liefern eine Liste dieser Zeilen. Optional ist die Angabe einer Kodierung, standardmäßig ist es StandardCharsets.UTF_8.

  • static String readString(Path path) throws IOException

  • static String readString(Path path, Charset cs) throws IOException

    Lesen eine Datei komplett aus und liefern den Inhalt als String. Ohne Kodierung gilt standardmäßig UTF-8. Beide Methoden sind neu in Java 11.

  • static Path write(Path path, byte[] bytes, OpenOption... options) throws IOException

    Schreibt ein Byte-Array in eine Datei.

  • static Path write(Path path, Iterable<? extends CharSequence> lines, OpenOption...

    options) throws IOException

  • static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs,

    OpenOption... options) throws IOException

    Schreiben alle Zeilen aus dem Iterable in eine Datei. Optional ist die Kodierung, die StandardCharsets.UTF_8 ist, so nicht anders angegeben.

  • static Path writeString(Path path, CharSequence csq, OpenOption... options) throws IOException

  • static Path writeString(Path path, CharSequence csq, Charset cs, OpenOption... options) throws IOException

    Schreiben eine Zeichenfolge in die genannte Datei. Der übergebene path wird zurückgegeben. Ohne Kodierung gilt standardmäßig UTF-8. Beide Methoden sind neu in Java 11.

Die Aufzählung OpenOption ist ein Vararg, und daher sind Argumente nicht zwingend nötig. StandardOpenOption ist eine Aufzählung vom Typ OpenOption mit Konstanten wie APPEND, CREATE usw.

[zB]  Beispiel

Lies eine UTF-8-kodierte Datei ein:

String s = Files.readString( path );

Bevor die praktische Methode in Java 11 einzog, sah eine Alternative so aus:

String s = new String( Files.readAllBytes( path ), StandardCharsets.UTF_8 );
[»]  Hinweis

Auch wenn es naheliegt, die Files-Methode zum Einlesen mit einem Path-Objekt zu füttern, das einen HTTP-URI repräsentiert, funktioniert dies nicht. So liefert schon die erste Zeile des Programms eine Ausnahme des Typs »java.nio.file.FileSystemNotFoundException: Provider ›http‹ not installed«.

URI uri = new URI( "http://tutego.de/javabuch/aufgaben/bond.txt" );

Path path = Paths.get( uri ); // inline image

List<String> content = Files.readAllLines( path );

System.out.println( content );

Vielleicht kommt in Zukunft ein Standard-Provider von Oracle, doch es ist davon auszugehen, dass quelloffene Lösungen diese Lücke schließen werden. Schwer zu programmieren sind Dateisystem-Provider nämlich nicht.

Ein Strom von Zeilen

Die bisherigen Files-Methoden lesen und schreiben als Ganzes, was ein Speicherproblem werden kann. Soll das Einlesen zeilenweise erfolgen, so ist statt readAllLines(), was im Speicher alle Zeilen als String-Objekte vorhält, ein Stream<String> eine gute Alternative. Zwei Methoden liefern einen Strom von Zeilen:

final class java.nio.file.Files
  • static Stream<String> lines(Path path)

  • Stream<String> lines(Path path, Charset cs)

    Liefern einen Stream von Zeilen einer Datei. Optional ist die Angabe der Kodierung, die sonst standardmäßig StandardCharsets.UTF_8 ist.

Datenströme kopieren

Sollen die Daten nicht direkt aus einer Datei in ein Byte-Array oder eine String-Liste gehen bzw. aus einem Byte-Array oder einer String-Sammlung in eine Datei, sondern von einer Datei in einen Datenstrom, so bieten sich zwei copy(…)-Methoden an:

final class java.nio.file.Files
  • static long copy(InputStream in, Path target, CopyOption… options)

    Entleert den Eingabestrom und kopiert die Daten in die Datei. Die Anzahl kopierter Bytes ist die Rückgabe.

  • static long copy(Path source, OutputStream out)

    Kopiert alle Daten aus der Datei in den Ausgabestrom. Die Anzahl kopierter Bytes ist die Rückgabe.

Im Zusammenhang mit Datenströmen kommen wir noch einmal auf diese beiden Methoden zurück.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern