Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
gp Kapitel 12 Datenströme und Dateien
  gp 12.1 Datei und Verzeichnis
    gp 12.1.1 Dateien und Verzeichnisse mit der Klasse File
    gp 12.1.2 Dateieigenschaften und -attribute
    gp 12.1.3 Dateien berühren, neue Dateien anlegen
    gp 12.1.4 Umbenennen und Verzeichnisse anlegen
    gp 12.1.5 Die Wurzel aller Verzeichnisse/Laufwerke
    gp 12.1.6 Verzeichnisse listen und Dateien filtern
    gp 12.1.7 Dateien und Verzeichnisse löschen
    gp 12.1.8 Implementierungsmöglichkeiten für die Klasse File
    gp 12.1.9 Verzeichnisse nach Dateien rekursiv durchsuchen
    gp 12.1.10 Sicherheitsprüfung
    gp 12.1.11 Namen der Laufwerke
    gp 12.1.12 Locking
  gp 12.2 Dateien mit wahlfreiem Zugriff
    gp 12.2.1 Ein RandomAccessFile öffnen
    gp 12.2.2 Aus dem RandomAccessFile lesen
    gp 12.2.3 Schreiben
    gp 12.2.4 Die Länge des RandomAccessFile
    gp 12.2.5 Hin und her in der Datei
  gp 12.3 Stream-Klassen und Reader/Writer
    gp 12.3.1 Die abstrakten Basisklassen
    gp 12.3.2 Übersicht über Ein-/Ausgabeklassen
  gp 12.4 Binäre Ein-/Ausgabe-Klassen InputStream und OutputStream
    gp 12.4.1 Die Klasse OutputStream
    gp 12.4.2 Ein Datenschlucker
    gp 12.4.3 Anwendung der Klasse FileOutputStream
    gp 12.4.4 Die Eingabeklasse InputStream
    gp 12.4.5 Anwenden der Klasse FileInputStream
    gp 12.4.6 Kopieren von Dateien
  gp 12.5 Daten filtern durch FilterInputStream und FilterOutputStream
    gp 12.5.1 Der besondere Filter PrintStream
    gp 12.5.2 Die Schnittstelle Appendable
    gp 12.5.3 System.in und System.out
  gp 12.6 Besondere OutputStream- und InputStream-Klassen
    gp 12.6.1 Bytes in den Strom schreiben mit ByteArrayOutputStream
    gp 12.6.2 Bytes in den Strom schreiben mit ByteArrayInputStream
    gp 12.6.3 Ströme zusammensetzen mit SequenceInputStream
  gp 12.7 Ressourcen wie Grafiken aus dem Klassenpfad und aus Jar-Archiven laden
  gp 12.8 Die Unterklassen von Writer
    gp 12.8.1 Die abstrakte Klasse Writer
    gp 12.8.2 Datenkonvertierung durch den OutputStreamWriter
    gp 12.8.3 In Dateien schreiben mit der Klasse FileWriter
    gp 12.8.4 StringWriter und CharArrayWriter
    gp 12.8.5 Writer als Filter verketten
    gp 12.8.6 Gepufferte Ausgabe durch BufferedWriter
    gp 12.8.7 Ausgabemöglichkeiten durch PrintWriter erweitern
    gp 12.8.8 Daten mit FilterWriter filtern
  gp 12.9 Die Klassen um Reader
    gp 12.9.1 Die abstrakte Basisklasse Reader
    gp 12.9.2 Automatische Konvertierungen mit dem InputStreamReader
    gp 12.9.3 Dateien lesen mit der Klasse FileReader
    gp 12.9.4 StringReader und CharArrayReader
  gp 12.10 Die Filter für Zeichenströme
    gp 12.10.1 Gepufferte Eingaben mit der Klasse BufferedReader
    gp 12.10.2 LineNumberReader zählt automatisch Zeilen mit
    gp 12.10.3 Eingaben filtern mit der Klasse FilterReader
    gp 12.10.4 Daten mit der Klasse PushbackReader zurücklegen
  gp 12.11 Kommunikation zwischen Threads mit Pipes
    gp 12.11.1 PipedOutputStream und PipedInputStream
    gp 12.11.2 PipedWriter und PipedReader
  gp 12.12 Datenkompression
    gp 12.12.1 Die Java-Unterstützung beim Komprimieren und Zusammenpacken
    gp 12.12.2 Datenströme komprimieren
    gp 12.12.3 Zip-Archive
    gp 12.12.4 Jar-Archive
  gp 12.13 Prüfsummen
    gp 12.13.1 Die Schnittstelle Checksum
    gp 12.13.2 Die Klasse CRC32
    gp 12.13.3 Die Adler32-Klasse
  gp 12.14 Persistente Objekte und Serialisierung
    gp 12.14.1 Objekte speichern mit der Standard-Serialisierung
    gp 12.14.2 Objekte über die Standard-Serialisierung lesen
    gp 12.14.3 Die Schnittstelle Serializable
    gp 12.14.4 Nicht serialisierbare Attribute mit transient aussparen
    gp 12.14.5 Das Abspeichern selbst in die Hand nehmen
    gp 12.14.6 Tiefe Objektkopien
    gp 12.14.7 Versionenverwaltung und die SUID
    gp 12.14.8 Wie die ArrayList serialisiert
    gp 12.14.9 Serialisieren in XML-Dateien
    gp 12.14.10 XML-API von Sun
  gp 12.15 Zugriff auf SMB-Server
    gp 12.15.1 jCIFS
  gp 12.16 Tokenizer
    gp 12.16.1 StreamTokenizer
    gp 12.16.2 CSV (Comma Separated Values)-Dateien verarbeiten
  gp 12.17 Die Logging-API

Kapitel 12 Datenströme und Dateien

Schlagfertigkeit ist jede Antwort, die so klug ist,
dass der Zuhörer wünscht, er hätte sie gegeben.
– Elbert Hubbard

Computer sind uns so nützlich, da sie Daten bearbeiten. Dieser Bearbeitungszyklus beginnt beim Einlesen der Daten, beinhaltet das Verarbeiten und lässt die Ausgabe folgen. In der deutschen Literatur taucht dies als EVA-Prinzip der Datenverarbeitungsanlagen auf. In frühen EDV-Zeiten wurde die Eingabe vom Systemoperator auf Lochkarten gestanzt, doch glücklicherweise sind diese Zeiten vorbei. Heutzutage speichern wir unsere Daten in Dateien (engl. files) und auch in Datenbanken ab. Wichtig zu bemerken ist, dass eine Datei nur durch den Kontext interessant ist, andernfalls beinhaltet sie für uns keine Information – die Sichtweise auf eine Datei ist demnach wichtig. Auch ein Programm besteht aus Daten und wird oft in Form einer Datei repräsentiert.

Um an die Information einer Datei zu kommen, müssen wir den Inhalt auslesen können. Zudem müssen wir in der Lage sein, Dateien anzulegen, zu löschen, umzubenennen und sie in Verzeichnissen zu strukturieren. Java bietet uns eine Vielzahl von Zugriffsmöglichkeiten auf Dateien und ein wichtiges Schlagwort ist hierbei der Datenstrom (engl. stream). Dieser entsteht beim Fluss der Daten von der Eingabe zur Verarbeitung hin zur Ausgabe. Durch Datenströme können Daten sehr elegant bewegt werden, ein Programm ohne Datenfluss ist eigentlich nicht denkbar. Die Eingabeströme (engl. input streams) sind zum Beispiel Daten der Tastatur, Datei oder dem Netzwerk, und über die Ausgabeströme (engl. output streams) fließen die Daten in ein Ausgabemedium, beispielsweise Drucker oder Datei. Die Kommunikation der Threads geschieht über Pipes. Sie sind eine spezielle Variante der Datenströme.

In Java sind über dreißig Klassen zur Verarbeitung der Datenströme vorgesehen. Da die Datenströme allgemein und nicht an ein spezielles Ein- oder Ausgabeobjekt gebunden sind, können sie untereinander beliebig gemischt werden. Dies ist vergleichbar mit dem elektrischen Strom. Es gibt mehrere Stromlieferanten (Solarkraftwerke, Nutzung geothermischer Energie, Umwandlung von Meereswärmeenergie (OTEC)) und mehrere Verbraucher (Wärmedecke, Mikrowelle), die die Energie wieder umsetzen.


Galileo Computing

12.1 Datei und Verzeichnidowntop

Da durch Datenströme keine Dateien gelöscht oder umbenannt werden können, liefert uns ein File-Objekt Informationen über Dateien und Verzeichnisse. Dieses Objekt wurde eingeführt, um Dateioperationen plattformunabhängig durchzuführen. Dies bedeutet aber leider auch eine Einschränkung, denn wie sollten Rechte vergeben werden, wenn etwa der Macintosh mit Mac OS 9 oder ein Palm-Pilot das nicht unterstützt? Auch Unix und Windows haben zwei völlig verschiedene Ansätze zur Rechteverwaltung.


Galileo Computing

12.1.1 Dateien und Verzeichnisse mit der Klasse File  downtop

Ein konkretes File-Objekt repräsentiert eine Datei oder ein Verzeichnis im Dateisystem. Der Verweis wird durch einen Pfadnamen spezifiziert. Dieser kann absolut oder relativ zum aktuellen Verzeichnis angegeben werden.


Hinweis   Pfadangaben sind plattformabhängig: Die Angabe des Pfads ist plattformabhängig, das heißt, auf Windows-Rechnern trennt ein Backslash die Pfade (»temp\doof«) und auf Unix-Maschinen ein normaler /, auch Divis genannt, (»temp/doof«). Glücklicherweise speichert die Klasse File im öffentlichen Attribut separatorChar den Dateitrenner.3 (Das kommt wiederum aus System.getProperty("file.separator".) Ebenso wie bei den Dateitrennern gibt es einen Unterschied in der Darstellung des Wurzelverzeichnisses. Unter Unix ist dies ein einzelnes Divis/«), und unter Windows ist die Angabe des Laufwerks vor den Doppelpunkt und das Backslash-Zeichen gestellt (»Z:\«).



class java.io.  File  
implements Serializable, Comparable<File>

Wir können ein File-Objekt durch fünf Konstruktoren erzeugen:

gp  File( String pathname )
Erzeugt ein File-Objekt mit komplettem Pfadnamen, zum Beispiel »d:\dali\die_anpassung_der_begierde«.
gp  File( String|File parent, String child )
Pfadname und Dateiname sind getrennt.
gp  File( URI uri )
Fragt von uri den Pfadnamen (uri.getPath()) und erzeugt ein neues File-Objekt. Ist uri null, folgt eine NullPointerException, ist die URI falsch formuliert, gibt es eine IllegalArgumentException.

URL-Objekte aus einem File-Objekt

Da es bei URL-Objekten recht häufig vorkommt, dass eine Datei die Basis ist, wurde die Methode toURL() der Klasse File aufgenommen. Es muss nur ein File-Objekt erzeugt werden, und anschließend erzeugt toURL() ein URL-Objekt, welches das Protokoll »file« trägt und eine absolute Pfadangabe zur Datei beziehungsweise zum Verzeichnis enthält. So liefert new File("C:/Programme/").toURL() das URL-Objekt mit der URL file:/C:/Programme/.

Da diese Methode das Trennzeichen für die Pfade beachtet, ist die Angabe demnach auch passend für die Plattform. Ein Verzeichnis endet passend mit dem Pfadtrenner.



class java.io.  File  
implements Serializable, Comparable<File>

gp  URL toURL() throws MalformedURLException
Liefert ein URL-Objekt vom File-Objekt.
gp  URI toURI()
Liefert ein URI-Objekt vom File-Objekt.

Galileo Computing

12.1.2 Dateieigenschaften und -attributdowntop

Eine Datei oder ein Verzeichnis besitzt zahlreiche Eigenschaften, die sich mit Anfragemethoden auslesen lassen. In einigen wenigen Fällen lassen sich die Attribute auch ändern.

gp  boolean canRead()
true, wenn wir lesend zugreifen dürfen
gp  boolean canWrite()
true, wenn wir schreibend zugreifen dürfen
gp  boolean exists()
true, wenn das File-Objekt existiert
gp  String getAbsolutePath()
Liefert den absoluten Pfad. Ist das Objekt kein absoluter Pfadname, so wird ein String aus aktuellem Verzeichnis, Separator-Zeichen und Dateinamen des Objekts verknüpft.
gp  String getCanonicalPath() throws IOException
File getCanonicalFile() throws IOException
Gibt den Pfadnamen des Dateiobjekts zurück, der keine relativen Pfadangaben mehr enthält. Kann im Gegensatz zu den anderen Pfadfunktionen eine IOException aufrufen, da mitunter verbotene Dateizugriffe erfolgen.
gp  String getName()
Gibt Dateinamen zurück.
gp  String getParent()
Gibt Pfadnamen des Vorgängers zurück.
gp  String getPath()
Gibt Pfadnamen zurück.
gp  boolean isAbsolute()
true, wenn der Pfad in der systemabhängigen Notation absolut ist
gp  boolean isDirectory()
Gibt true zurück, wenn es sich um ein Verzeichnis handelt.
gp  boolean isFile()
true, wenn es sich um eine »normale« Datei handelt (kein Verzeichnis und keine Datei, die vom zugrunde liegenden Betriebssystem als besonders markiert wird; Blockdateien, Links unter Unix). In Java können nur normale Dateien erzeugt werden.
gp  long length()
Gibt die Länge der Datei in Bytes zurück oder 0L, wenn die Datei nicht existiert oder es sich um ein Verzeichnis handelt.

Beispiel   Um festzustellen, ob f das Wurzelverzeichnis ist, genügt folgende Zeile:

if ( f.getPath().equals(f.getParent()) )
 // File f ist root


Beispiel   Liefere einen Dateinamen, bei dem die relativen Bezüge aufgelöst sind.

try
{
  System.out.println( new File("C:/./WasNDas//..\\Programme").getCanonicalFile() );
}
catch ( IOException e ) { }

Das Ergebnis ist C:\Programme.


Änderungsdatum einer Datei

Eine Datei besitzt unter jedem Dateisystem nicht nur Attribute wie Größe und Rechte, sondern verwaltet auch das Datum der letzten Änderung. Letzteres nennt sich Zeitstempel. Die File-Klasse besitzt zum Abfragen dieser Zeit die Methode lastModified(). Mit der Methode setLastModified() lässt sich die Zeit auch setzen. Dabei bleibt es etwas verwunderlich, warum lastModified() nicht als veraltet ausgezeichnet ist und zu getLastModified() geworden ist, wo doch nun die passende Funktion zum Setzen der Namensgebung genügt.



class java.io.  File  
implements Serializable, Comparable<File>

gp  long lastModified()
Liefert den Zeitpunkt, an dem die Datei zum letzten Mal geändert wurde. Die Zeit wird in Millisekunden ab dem 1. Januar 1970, 00:00:00 GMT gemessen. Die Methode liefert null, wenn die Datei nicht existiert oder ein Ein- beziehungsweise Ausgabefehler auftritt.
gp  boolean setLastModified( long time )
Setzt die Zeit, an dem die Datei zuletzt geändert wurde. Die Zeit ist wiederum in Millisekunden seit dem 1. Januar 1970 angegeben. Ist das Argument negativ, dann wird eine IllegalArgumentException ausgeworfen.

Die Methode setLastModified() ändert – wenn möglich – den Zeitstempel, und ein anschließender Aufruf von lastModified() liefert die gesetzte Zeit – womöglich gerundet – zurück. Die Funktion ist von vielfachem Nutzen, aber sicherheitsbedenklich. Denn ein Programm kann den Dateiinhalt ändern und den Zeitstempel dazu. Auf den ersten Blick ist nicht mehr sichtbar, dass eine Veränderung der Datei vorgenommen wurde. Doch die Funktion ist von größerem Nutzen bei der Programmerstellung, wo Quellcodedateien etwa mit Objektdateien verbunden sind. Nur über einen Zeitstempel ist eine einigermaßen intelligente Projektdateiverwaltung möglich.


Hinweis: Zwar erlaubt Java, die die Zeit des letzten Zugriffs zu ermitteln, das gilt aber nicht für die Erzeugungszeit. Das ist nicht böswillig, denn ein Unix-System speichert diese Zeit zum Beispiel gar nicht. Windows speichert diese Zeit schon, so dass hier grundsätzlich der Zugriff, etwa über JNI, möglich wäre. Legt ein Java-Programm die Dateien an, dessen Anlegezeiten später wichtig sind, müssen die Zeiten beim Anlegen gemessen und gespeichert werden. Falls die Datei nicht verändert wird, stimmt lastModified() mit der Anlegzeit überein.


Galileo Computing

12.1.3 Dateien berühren, neue Dateien anlegen  downtop

Unter dem Unix-System gibt es das Shell-Kommando touch, welches wir in einer einfachen Variante in Java umsetzen wollen. Das Programm berührt (engl. touch) eine Datei, indem der Zeitstempel auf das aktuelle Datum gesetzt wird. Da es mit setLastModified() einfach ist, das Zeitattribut zu setzen, muss die Datei nicht geöffnet, das erste Byte gelesen und gleich wieder geschrieben werden. Wie beim Kommando touch soll unser Java-Programm über alle auf der Kommandozeile übergebenen Dateien gehen und sie berühren. Falls eine Datei nicht existiert, soll sie kurzerhand angelegt werden. Gibt setLastModified() den Wahrheitswert false zurück, so wissen wir, dass die Operation fehlschlug, und geben eine Informationsmeldung aus.

Listing 12.1 Listing 12.1   Touch.java


import java.io.*;

public class Touch
{
  public static void main( String args[] ) throws IOException
  {
    for ( int i = 0; i < args.length; i++ )
    {
      File f = new File( args[i] );

      if ( f.exists() )
      {
        if ( f.setLastModified( System.currentTimeMillis() ) )
          System.out.println( "touched " + args[i] );
        else
          System.out.println( "touch failed on " + args[i] );
      }
      else
      {
        f.createNewFile();
        System.out.println( "create new file " + args[i] );
      }
    }
  }
}


class java.io.  File  
implements Serializable, Comparable<File>

gp  boolean createNewFile() throws IOException
Legt eine neue Datei, wenn noch keine existiert.
gp  static File createTempFile( String prefix, String suffix ) throws IOException
Legt eine neue Datei im temporären Verzeichnis an. Das Verzeichnis ist etwa /temp unter Unix oder c:/temp unter Windows. Der Dateiname setzt sich zusammen aus einem benutzerdefinierten Präfix, einer Zufallsfolge und einem Suffix.
gp  static File createTempFile( String prefix, String suffix, File directory )
throws IOException
Legt eine neue Datei im gewünschten Verzeichnis an. Der Dateiname setzt sich zusammen aus einem benutzerdefinierten Präfix, einer Zufallsfolge und einem Suffix.

Galileo Computing

12.1.4 Umbenennen und Verzeichnisse anlegen  downtop

Mit mkdir() lassen sich Verzeichnisse anlegen und mit renameTo() Dateien oder Verzeichnisse umbenennen.

class java.io.  File  
implements Serializable, Comparable<File>

gp  boolean mkdir()
Legt das Unterverzeichnis an.
gp  boolean mkdirs()
Legt das Unterverzeichnis inklusive weiterer Verzeichnisse an.
gp  boolean renameTo( File d )
Benennt die Datei in den Namen um, der durch das File-Objekt d gegeben ist. Ging alles gut, wird true zurückgegeben. Bei zwei Dateinamen alt und neu benennt new File(alt).renameTo(new File(neu)); die Datei um.

Über renameTo() sollte noch ein Wort verloren werden: File-Objetke sind immutable, stehen also immer nur für genau eine Datei. Ändert sich der Dateiname, ist das File-Objekt ungültig und kein Zugriff mehr über dieses File-Objekt erlaubt. Auch wenn eine Laufzeitumgebung keine Exception auswirft, sind alle folgenden Ergebnisse von Anfragen unsinnig.

Zum Teil kann renameTo() auch zum Verschieben von Dateien dienen. Oftmals gilt aber die Einschränkung, das dies nur auf einem Datenträger möglich ist (also etwa von Laufwerk C nach C:/, aber nicht von C nach D:/), Links und sonstigen Dateivarianten nicht eingerechnet.


Galileo Computing

12.1.5 Die Wurzel aller Verzeichnisse/Laufwerke  downtop

Die statische Funktion listRoots() gibt ein Feld von File-Objekten zurück, die eine Auflistung der Wurzeln (engl. root) von Dateisystemen enthält. Dies macht es einfach, Programme zu schreiben, die etwa über dem Dateisystem eine Suche ausführen. Da es unter Unix nur eine Wurzel gibt, ist der Rückgabewert von File.listRoots() immer »/« – ein anderes Root gibt es nicht. Unter Windows wird es aber zu einem richtigen Feld, da es mehrere Wurzeln für die Partitionen oder logischen Laufwerke gibt. Die Wurzeln tragen Namen wie »A:« oder »Z:«. Dynamisch eingebundene Laufwerke, die etwa unter Unix mit »mount« integriert werden, oder Wechselfestplatten werden mit berücksichtigt. Die Liste wird immer dann aufgebaut, wenn listRoots() aufgerufen wird. Komplizierter ist es, wenn entfernte Dateibäume mittels NFS oder SMB eingebunden sind. Denn dann kommt es darauf an, ob das zuständige Programm eine Verbindung noch aktiv hält oder nicht. Denn nach einer abgelaufenen Zeit ohne Zugriff wird das Verzeichnis wieder aus der Liste genommen. Dies ist aber wieder sehr plattformabhängig.



class java.io.  File  
implements Serializable, Comparable<File>

gp  static File[] listRoots()
Liefert die verfügbaren Wurzeln der Dateisysteme oder null, falls diese nicht festgestellt werden können. Jedes File-Objekt beschreibt eine Dateiwurzel. Es ist gewährleistet, dass alle kanonischen Pfadnamen mit einer der Wurzeln beginnen. Wurzeln, für die der SecurityManager den Zugriff verweigert, werden nicht aufgeführt. Das Feld ist leer, falls es keine Dateisystem-Wurzeln gibt.

Liste der Wurzeln/Laufwerke ausgeben

Im folgenden Beispiel wird ein Programm vorgestellt, das mit listRoots() eine Liste der verfügbaren Wurzeln ausgibt. Dabei berücksichtigt das Programm, ob auf das Gerät eine Zugriffsmöglichkeit besteht. Unter Windows ist etwa ein Diskettenlaufwerk eingebunden, aber wenn keine Diskette im Schacht ist, ist das Gerät nicht bereit. Das Diskettenlaufwerk taucht in der Liste auf, aber exists() liefert false.

Listing 12.2   ListRoots.java


import java.io.*;

public class ListRoots
{
  public static void main( String args[] )
  {
    for ( File file : File.listRoots() )
      System.out.println( file.getPath() + " ist " +
                          (file.exists() ? "" : "nicht ") + "bereit" );  }
}

Bei der Ausgabe mit System.out.println() entspricht root.getPath() einem root.toString(). Demnach könnte das Programm etwas abgekürzt werden, etwa mit root + " XYZ". Da aber nicht unbedingt klar ist, dass toString() auf getPath() verweist, schreiben wir getPath() direkt.


Galileo Computing

12.1.6 Verzeichnisse listen und Dateien filterdowntop

Um eine Verzeichnisanzeige oder einen Dateiauswahldialog zu programmieren, benötigen wir eine Liste von Dateien, die in einem Verzeichnis liegen. Ein Verzeichnis kann reine Dateien oder auch wieder Unterverzeichnisse besitzen. Die list()- und listFiles()-Funktionen der Klasse File geben ein Feld von Zeichenketten mit Dateien und Verzeichnissen beziehungsweise ein Feld von File-Objekten mit den beinhalteten Elementen zurück.



class java.io.  File  
implements Serializable, Comparable<File>

gp  String[] list()
Gibt eine Liste der Dateien zurück. Diese enthält weder ».« noch »..«.
gp  File[] listFiles()
Gibt eine Liste der Dateien als File-Objekte zurück.

Beispiel   Ein einfacher Directory-Befehl ist leicht in ein paar Zeilen programmiert.

String entries[] = new File(".").list();
System.out.println( Arrays.asList(entries) );

Die einfache Funktion list() liefert dabei nur relative Pfade, also einfach den Dateinamen oder den Verzeichnisnamen. Den absoluten Namen zu einer Dateiquelle müssen wir also erst zusammensetzen. Praktischer ist da schon die Methode listFiles(), da wir hier komplette File-Objekte bekommen, die ihre ganze Pfadangabe schon kennen. Wir können den Pfad mit getName() erfragen.

Dateien nach Kriterien filtern mit FilenameFilter und FileFilter

Sollen aus einer Liste von Dateien einige mit bestimmten Eigenschaften (zum Beispiel der Endung) herausgenommen werden, so müssen wir dies nicht selbst programmieren. Schlüssel hierzu ist die Schnittstelle FilenameFilter und FileFilter. Wenn wir etwas später den grafischen Dateiselektor kennen lernen, so können wir dort auch den FilenameFilter einsetzen. Leider ließ der Fehlerteufel seine Finger nicht aus dem Spiel und der FilenameFilter funktioniert nicht, da der FileSelector fehlerhaft ist.

Ein Filter filtert aus den Dateinamen diejenigen heraus, die einem gesetzten Kriterium genügen. Eine Möglichkeit ist, nach den Endungen zu separieren. Doch auch komplexere Selektionen sind denkbar; so kann in die Datei hineingesehen werden, ob sie beispielsweise bestimmte Informationen am Dateianfang enthält. Besonders für Macintosh-Benutzer ist dies wichtig zu wissen, denn dort sind die Dateien nicht nach Endungen sortiert. Die Information liegt in den Dateien selbst. Windows versucht uns auch diese Dateitypen vorzuenthalten, aber von dieser Kennung hängt alles ab. Wer die Endung einer Grafikdatei schon einmal umbenannt hat, der weiß, warum Grafikprogramme aufgerufen werden. Von den Endungen hängt also sehr viel ab.



class java.io.  File  
implements Serializable, Comparable<File>

gp  public String[] list( FilenameFilter filter )
Wie list(), nur filtert ein spezielles FilenameFilter-Objekt Objekte heraus.
gp  public File[] listFiles( FilenameFilter filter )
Wie listFiles(), nur filtert ein spezielles FilenameFilter-Objekt Objekte heraus.
gp  public File[] listFiles( FileFilter filter )
Wie list(), nur filtert ein spezielles FileFilter-Objekt bestimmte Objekte heraus.


interface java.io.  FileFilter  

gp  boolean accept( File pathname )
Liefert true, wenn der Pfad in die Liste aufgenommen werden soll.


interface java.io.  FilenameFilter  

gp  boolean accept( File dir, String name )
Testet, ob die Datei name im Verzeichnis dir in der Liste auftreten soll. Gibt true zurück, wenn dies der Fall ist.

Eine Filter-Klasse implementiert die Funktion accept() von FilenameFilter so, dass alle von accept() angenommenen Dateien den Rückgabewert true liefern.


Beispiel   Wollen wir nur auf Textdateien reagieren, so geben wir ein true bei allen Dateien mit der Endung .txt zurück. Die anderen werden mit false abgelehnt.

class FileFilter implements FilenameFilter
{
  public boolean accept( File f, String s )
  {
    return s.toLowerCase().endsWith( ".txt" );
  }
}

Nun kann list() mit dem FilenameFilter aufgerufen werden. Wir bekommen eine Liste mit Dateinamen, die wir in einer Schleife einfach ausgeben. An dieser Stelle merken wir schon, dass wir nur für FilenameFilter eine neue Klasse schreiben müssen. Hier bietet sich wieder eine innere Klasse an.


Beispiel   list() soll nur Verzeichnisse zurückliefern:

String a[] = entries.list( new FilenameFilter() {
  public boolean accept( File d, String name ) {
    return d.isDir();
  } } );

Die einfache Implementierung von list() mit FilenameFilter

Die Methode list() holt zunächst ein Feld von Dateinamen ein. Nun wird jede Datei mittels der accept()-Methode geprüft und in eine interne ArrayList übernommen. Nach dem Testen jeder Datei wird das Array mit ((String[])(v.toArray(new String[0])) in ein Feld von Strings kopiert. Bei listFiles() steht anstelle von String dann File.

Dateien aus dem aktuellen Verzeichnis filtern

Wir können somit ein einfaches Verzeichnisprogramm programmieren, indem wir die Funktionen von getProperty() und list() zusammenfügen. Zusätzlich wollen wir nur Dateien mit der Endung .txt angezeigt bekommen.

Listing 12.3   Dir.java


import java.io.*;

class TxtFilenameFilter implements FilenameFilter
{
  public boolean accept( File f, String s )
  {
    return s.toLowerCase().endsWith(".txt");
  }
}

public class Dir
{
  public static void main( String args[] )
  {
    File userdir = new File( System.getProperty("user.dir") );
    System.out.println( userdir );

    String entries[] = userdir.list( new TxtFilenameFilter() );

    for ( int i = 0; i < entries.length; i++ )
      System.out.println( entries[i] );
  }
}

Galileo Computing

12.1.7 Dateien und Verzeichnisse löschen  downtop

Mit Hilfe der Funktion delete() auf einem File-Objekt lässt sich eine Datei oder ein Verzeichnis entfernen. Diese Methode löscht wirklich! Sie ist nicht so zu verstehen, dass sie true liefert, falls die Datei potenziell gelöscht werden kann. Konnte die Laufzeitumgebung delete() nicht ausführen, so sollte die Rückgabe false sein. Ein zu löschendes Verzeichnis muss leer sein, andernfalls kann das Verzeichnis nicht gelöscht werden. Unsere unten stehende Implementierung geht dieses Problem so an, dass es rekursiv die Unterverzeichnisse löscht.


Hinweis   Auf manchen Systemen liefert delete() die Rückgabe true, die Datei ist aber nicht gelöscht. Grund kann eine noch geöffnete Datei sein, mit der zum Beispiel ein Eingabestrom verbunden ist.



class java.io.  File  
implements Serializable, Comparable<File>

gp  boolean delete()
Löscht die Datei oder das leere Verzeichnis.
gp  void deleteOnExit()
Löscht die Datei/das Verzeichnis, wenn die virtuelle Maschine korrekt beendet wird. Einmal vorgeschlagen, kann das Löschen nicht mehr rückgängig gemacht werden.

Die delete()-Funktion funktioniert auf File-Objekten, die Dateien und auch Verzeichnisse repräsentieren. Doch sind Verzeichnisse nicht leer, so wird delete() nicht auch noch alle Dateien in diesem Verzeichnis inklusive aller Unterverzeichnisse löschen. Das muss von Hand gemacht werden, lässt sich aber in wenigen Programmcodezeilen rekursiv umsetzen. Eine Funktion deleteTree() soll einen Baum inklusive Unterverzeichnisse löschen. list() liefert ein Feld aller Elemente in dem Unterverzeichnis, und falls ein Element wiederum ein Verzeichnis ist, wird wieder deleteTree() auf diesem aufgerufen.

Listing 12.4   DeleteTree.java


import java.io.*;

public class DeleteTree
{
  public static void deleteTree( File path )
  {
    for ( File file : path.listFiles() )
    {
      if ( file.isDirectory() )
        deleteTree( file );

      file.delete();
    }

    path.delete();
  }

  public static void main( String args[] )
  {
    deleteTree( new File("c:/temp/Kopie von kai") );
  }
}

Galileo Computing

12.1.8 Implementierungsmöglichkeiten für die Klasse File  downtop

Wir sollten für die Klasse File noch einmal festhalten, dass sie lediglich ein Verzeichnis oder eine Datei im Verzeichnissystem repräsentiert, jedoch keine Möglichkeit bietet, auf die Daten selbst zuzugreifen. Es ist ebenso offensichtlich, dass wir mit den plattformunabhängigen Eigenschaften von Java spätestens bei Zugriffen auf das Dateisystem nicht mehr weiter kommen. Wenn zum Beispiel eine Datei gelöscht werden soll oder wir eine Liste von Dateien im Verzeichhnis erbitten, sind Betriebssystemfunktionen mit von der Partie. Diese sind dann nativ programmiert. Es bieten sich zwei Möglichkeiten für die Implementierung an.

gp  Zunächst ist es denkbar, die nativen Methoden in der Klasse File selbst anzugeben. Diesen Weg ging die Sun-Implementierung eine ganze Zeit lang, und Kaffe nutzt diese Variante heute noch. Doch die Verschmelzung von einem Dateisystem in der Klasse File bietet auch Nachteile. Was ist, wenn das Dateisystem eine Datenbank ist, so dass die typischen nativen C-Funktionen unpassend sind?
gp  Aus dieser Beschränkung heraus hat sich Sun dazu entschlossen, eine nur paketsichtbare, abstrakte Klasse FileSystem einzufügen, die ein abstraktes Dateisystem repräsentiert.

Jedes Betriebssystem implementiert folglich eine konkrete Unterklasse für FileSystem, die dann von File genutzt werden kann. File ist dann völlig frei von nativen Methoden und leitet alles an das FileSystem-Objekt weiter, das intern mit folgender Zeile angelegt wird:


static private FileSystem fs = FileSystem.getFileSystem();

Dann ergibt sich zum Beispiel für den Zugriff auf die Länge einer Datei:


public long length() {
  SecurityManager security = System.getSecurityManager();
  if ( security != null )
    security.checkRead( path );
   return fs.getLength( this );
}

Wenn wir dies noch einmal mit dem ersten Weg vergleichen, dann finden wir in der File-Implementierung von Kaffe etwa Folgendes:


public class File implements Serializable, Comparable
{
  static {
    System.loadLibrary( "io" );
  }

  public long length() {
    checkReadAccess();
    return length0();
  }

  native private long length0();
}

Die native Methode ist selbstverständlich privat, denn sonst gäbe es ja keine Sicherheitsprüfung. Oft trägt sie die Endung 0, so dass eine Unterscheidung einfach ist.

Um das Geheimnis um die native Methode length0() zu lüften und einen Eindruck von nativen Methoden zu vermitteln, gönnen wir uns einen Blick auf die Implementierung:


jlong
java_io_File_length0( struct Hjava_io_File* this )
{
  struct stat buf;
  char str[MAXPATHLEN];
  int r;

  stringJava2CBuf( unhand(this)->path, str, sizeof(str) );

  r = KSTAT( str, &buf );
  if ( r != 0 )
    return (jlong)0;

  return (jlong)buf.st_size;
}

Der Aufruf stringJava2CBuf() konvertiert die Unicode-Zeichenfolge in eine gültige C–Zeichenkette, die mit einem Null-Byte abschließt. Es folgt der Aufruf einer ANSI-Bibliotheksfunktion, die noch über KSTAT gekapselt ist. Der Datentyp jlong ist kein C(++)-Datentyp, sondern in der javaspezifischen Header-Datei definiert.

Wenn wir uns etwas später mit dem Zugriff über Stream-Klassen beschäftigen, dann benutzt auch diese Klasse native Methoden und eine abstrakte Repräsentation eines Dateideskriptors.


Galileo Computing

12.1.9 Verzeichnisse nach Dateien rekursiv durchsuchen  downtop

Im vorausgehenden Kapitel haben wir einige Datenstrukturen kennen gelernt, unter anderem Vector und Stack. Wir wollen damit ein Programm formulieren, welches rekursiv die Verzeichnisse durchläuft und nach Dateien durchsucht. Die Vector-Klasse dient dazu, die Dateien zu speichern, und mit dem Stack merken wir uns die jeweiligen Verzeichnisse (Tiefensuche), in die wir absteigen. Anders als bei DeleteTree nutzt diese Implementierung also keine Rekursion.

Listing 12.5   FileFinder.java


import java.io.*;
import java.util.*;

public class FileFinder
{
  public List<File> files = new ArrayList<File>( 1024 );

  public static void main( String args[] )
  {
    String path = new File(System.getProperty("user.dir")).getParent();

    System.out.println( "Looking in path: " + path );

    FileFinder ff = new FileFinder( path,  // TODO: 1.5
                                    new String[] { ".gif", ".jpg", ".tif" } );
    ff.print();
  }

  public FileFinder( String start, String extensions[] )  // TODO: Java 5 mit ...
  {
    Stack<File> dirs = new Stack<File>();

    File startdir = new File( start );

    if ( startdir.isDirectory() )
      dirs.push( startdir );

    while ( dirs.size() > 0 )
    {
      for ( File file : dirs.pop().listFiles() )
      {
        if ( file.isDirectory() )
          dirs.push( file );
        else
          if ( match(file.getName(), extensions) )
            files.add( file );
      }
    }
  }

  public void print()
  {
    System.out.println( "Found " + files.size() + " file" +
                         (files.size() == 1 ? "." : "s.") );

    for ( File f : files )
      System.out.println( f.getAbsolutePath() );
  }

  private static boolean match( String s, String suffixes[] ) 
// In Java 5 ... möglich
  {
    for ( String suffix : suffixes )
      if ( s.length() >= suffix.length() &&
           s.substring(s.length()suffix.length(),
                       s.length()).equalsIgnoreCase(suffix) )
        return true;

    return false;
  }
}

Galileo Computing

12.1.10 Sicherheitsprüfung  downtop

Wir müssen uns bewusst sein, dass verschiedene Methoden eine SecurityException auslösen können, sofern ein Security-Manager die Dateioperationen überwacht. Security-Manager kommen beispielsweise bei Applets zum Zuge. Folgende Methoden sind Kandidaten für eine SecurityException: exists(), canWrite(), canRead(), canWrite(), isDirectory(), lastModified(), length(), mkdir(), mkdirs(), list(), delete() und renameFile().


Galileo Computing

12.1.11 Namen der Laufwerke  downtop

Die Namen der Laufwerksbuchstaben sind ein wenig versteckt, denn es ist nicht bei der Klasse File zu finden. Zwar liefert listRoots() schon einen passenden Anfang, um unter Windows die Laufwerke preiszugeben, aber die Namen liefert erst getSystemDisplayName() des FileSystemView-Objekts. Die Klasse gehört zu Swing und dort zum Dateiauswahldialog.


FileSystemView view = FileSystemView.getFileSystemView();
for ( File f : File.listRoots() )
  System.out.println( view.getSystemDisplayName(f) );

FileSystemView hält noch andere gute Funktionen bereit:



abstract class javax.swing.filechooser.FileSystemView

gp  boolean isDrive( File dir )
Ist dir ein Laufwerk?
gp  boolean isFloppyDrive( File dir )
Ist dir ein Wechsellaufwerk?
gp  boolean isComputerNode( File dir )
Ist dir ein Netzwerk-Knoten?

Galileo Computing

12.1.12 Locking  toptop

Damit eine Datei gegen parallelen Zugriff gesperrt ist, kann man sie Locken. Das ist seit 1.4 auf machen Betriebssystemen möglich. Ein Betriebssystem wie Unix macht es im Allgemeinen schwer, da es keine Locks unterstützt. So kann unter Unix eine Datei auch von mehreren Seiten gelesen werden, auch wenn sie zum Beispiel aktuell beschrieben wird. Auch kann eine Datei auf dem Datensystem gelöscht werden, wenn sich noch geöffnet ist. Das Windows Betriebssystem unterstützt dagegen Locks. Wenn ein Prozess keinen Lock auf die Datei besitzt, kann der Prozess die Datei auch nicht lesen.

Um einen Lock zu erwerben, ist die Klasse FileChannel und deren Funktion lock() zu nutzen. Um zu testen, ob eine gegebene Datei gelockt ist, lässt sich tryLock() verwenden – etwa in folgender Funktion:


public boolean isLocked( String filename )
{
  try
  {
    new RandomAccessFile( filename,"r" ).getChannel().tryLock();
  }
  catch( IOException e ) {
     return false;
  }
  return true;
}

Falls nun Locking unter Unix-Systemen gewünscht ist, bleibt nichts anderes übrig, als das Locking nachzuimplementieren. Das gelingt durch Umkopieren oder Umbenennen.






1   EVA ist ein Akronym für »Eingabe, Verarbeitung, Ausgabe«. Diese Reihenfolge entspricht dem Arbeitsweg. Zunächst werden die Eingaben von einem Eingabegerät gelesen, dann durch den Computer verarbeitet und anschließend ausgegeben (in welcher Form auch immer).

2   Das englische Wort »file« geht auf das lateinische Wort filum zurück. Dies bezeichnete früher eine auf Draht aufgereihte Sammlung von Schriftstücken.

3   Ein Wunder, warum das nicht großgeschrieben ist! Dabei ist die Variable public static final char, also eine waschechte Konstante.

4   Obwohl die Funktionalität dokumentiert ist, findet sich unter der Bug-Nummer 4031440 kurz: »The main issue is that support for FilenameFilter in the FileDialog class was never implemented on any platform – it’s not that there’s a bug which needs to be fixed, but that there’s no code to run nor was the design ever evaluated to see if it *could* be implemented on our target platforms«.





Copyright © Galileo Press GmbH 2004
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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de