![]() |
|
|||||
Änderungsdatum einer DateiEine 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 dem JDK 1.2 ist die Methode setLastModified() hinzugekommen, um auch diese Zeit zu setzen. Dabei bleibt es etwas verwunderlich, warum lastModified() nicht veraltet ausgezeichnet ist und zu getLastModified() geworden ist, wo doch nun die passende Funktion zum Setzen der Namensgebung genügt.
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, ist aber sicherheitsbedenklich. Denn ein Programm kann den Dateiinhalt ändern und den Zeitstempel dazu. Anschließend ist von außen 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. Dateien berührenUnter dem UNIX-System gibt es das Shellkommando 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. 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] ); } } } } SicherheitsprüfungWir müssen uns bewusst sein, dass verschiedene Methoden, unter der Bedingung, dass ein Security-Manager die Dateioperationen überwacht, eine SecurityException auslösen können. 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(). 12.1.3 Umbenennen, Verzeichnisse anlegen und Datei löschen
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class java.io.File |
| boolean mkdir() Legt das Unterverzeichnis an. |
| boolean mkdirs() Legt das Unterverzeichnis inklusive weiterer Verzeichnisse an. |
| 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. |
| boolean delete() true, wenn die Datei gelöscht werden konnte. Ein zu löschendes Verzeichnis muss leer sein. Diese Methode löscht wirklich. Sie ist nicht so zu verstehen, dass sie true liefert, falls die Datei potenziell gelöscht werden kann. |
Die statische Methode listRoots()3 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 |
| 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. |
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.
import java.io.*; public class ListRoots { public static void main( String args[] ) { File list[] = File.listRoots(); for ( int i = 0; i < list.length; i++ ) System.out.println( list[i].getPath() + " ist " + (list[i].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.
Um eine Verzeichnisanzeige 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 beziehungsweise ein Feld von File-Objekten zurück.
class java.io.File |
| String[] list() Gibt eine Liste der Dateien zurück. Diese enthält weder ».« noch »..«. |
| File[] listFiles() Gibt eine Liste der Dateien als File-Objekte zurück. |
Beispiel Ein einfacher Directory-Befehl ist somit 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.
Wollen wir den Inhalt des aktuellen Verzeichnisses lesen, so müssen wir irgendwie an den absoluten Pfad kommen, um damit das File-Objekt zu erzeugen. Dazu müssten wir über die System-Properties gehen. Die System-Properties verwalten die systemabhängigen Variablen, unter anderem auch den Pfadseparator und das aktuelle Verzeichnis.
final class java.lang.System |
| String getProperty( String ) Holt den bestimmten Property-Eintrag für ein Element. |
Beispiel Über die System-Properties und den Eintrag »user.dir« gelangen wir ans aktuelle Benutzerverzeichnis und list() wird wie folgt ausgewertet:
File userdir = new File( System.getProperty("user.dir") ); String entries[] = userdir.list(); for ( int i = 0; i < entries.length; i++ ) System.out.println( entries[i] ); |
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.4
Ein Filter filtert aus den Dateinamen diejenigen heraus, die einem gesetzten Kriterium genügen oder nicht. 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 |
| public String[] list( FilenameFilter filter ) Wie list(), nur filtert ein spezielles FilenameFilter-Objekt Objekte heraus. |
| public File[] listFiles( FilenameFilter filter ) Wie listFiles(), nur filtert ein spezielles FilenameFilter-Objekt Objekte heraus. |
| public File[] listFiles( FileFilter filter ) Wie list(), nur filtert ein spezielles FileFilter-Objekt bestimmte Objekte heraus. |
Der Unterschied zwischen list() und listFiles() liegt also darin, dass list() ein Feld vom String-Objekt liefert und listFiles() ein Feld von File-Objekten.
interface java.io.FileFilter |
| boolean accept( File pathname ) Liefert true, wenn der Pfad in die Liste aufgenommen werden soll. |
interface java.io.FilenameFilter |
| 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 angenommenen Dateien den Rückgabewert true liefern.
Beispiel Wollen wie 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. An dieser Stelle bietet sich wieder eine innere Klasse an.
Beispiel Zusammen mit list() ergibt sich folgender Programmcode, um alle Verzeichnisse auszufiltern:
String a[] = entries.list( new FilenameFilter() { public boolean accept( File d, String name ) { return d.isDir(); } } ); |
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.
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.
import java.io.*; class TxtFilenameFilter implements FilenameFilter { public boolean accept( File f, String s ) { return s.toLowerCase().endsWith(".txt"); } } 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] ); } }
Wollen wir alle Dateien eines Verzeichnisses in einer Liste verwalten, so müssen wir erst einen FileFilter programmieren, dann das Feld noch einmal durchlaufen und anschließend die Liste aufbauen. Der Filter lässt sich auch gut dafür gebrauchen, gleich die Liste aufzubauen. Dazu interessieren wir uns einfach nicht für den Rückgabewert von listFiles(), sondern fügen die in der accept()-Methode übergebenen File-Objekte gleich in die Liste ein.
final ArrayList list = new ArrayList( 512 ); new File( path ).listFiles( new FileFilter() { public boolean accept( File f ) { if ( f.isFile() ) { list.add( f ); return true; } return false; } } ); Collections.shuffle( list );
Wir haben das Problem mit einer inneren Klasse gelöst, da wir für den Filter nur eine kleine Methode programmieren müssen. Die einzige Besonderheit liegt in der Liste list. Sie ist lokal für die Methode, und da die innere Klasse nicht auf einfache lokale Variablen zugreifen kann, muss sie final sein. Dann kopiert der Compiler automatisch die Referenz auf die LinkedList in die innere Klasse. Die letzte Zeile zeigt für Collection-Objekte – und List ist eine Collection – was es für Felder nicht gibt: durcheinander würfeln der Einträge. Mit Collections.sort() können wir auch sortieren. Für Felder gibt es Arrays.sort().
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 Pfad erbitten, sind Betriebssystemfunktionen mit von der Partie. Diese sind dann nativ programmiert. Es bieten sich zwei Möglichkeiten für die Implementierung an. 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, sodass die typischen nativen C-Funktionen unpassend sind? Aus dieser Beschränkung heraus hat sich Sun dazu entschlossen, eine nur paketsichtbare, abstrakte Klasse FileSystem einzufügen, die ein abstraktes Dateisystem repräsentiert. Das 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. Oft trägt sie die Endung 0, sodass 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. Jetzt folgt nur noch der Aufruf einer ANSI-Bibliotheksfunktion, die noch einmal ü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 tatsächlichen Zugriff beschäftigen, dann benutzt auch diese Klasse native Methoden und eine abstrakte Repräsentation eines Dateideskriptors.
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 Vektor-Klasse dient dazu, die Dateien zu speichern, und mit dem Stack merken wir uns die jeweiligen Verzeichnisse (Tiefensuche), in die wir absteigen.
import java.io.*; import java.util.*; public class FileFinder { public static void main( String args[] ) { String suffix[] = { ".gif", ".jpg", ".tif" } ; String userDir = System.getProperty("user.dir"), path = new File(userDir).getParent(); System.out.println( "Looking in path:" + path ); FileFinder ff = new FileFinder( path, suffix ); ff.print(); } public void print() { int noFiles = files.size(); if ( noFiles == 0 ) { System.out.println( "No files found." ); return; } System.out.print( "Found " + noFiles + " file" ); if ( noFiles != 1 ) System.out.println( "s." ); else System.out.println( "." ); for ( int i = 0; i < noFiles; i++ ) { String path; path = ((File)files.elementAt(i)).getAbsolutePath(); System.out.println( path ); } } public FileFinder( String start, String extensions[] ) { files = new Vector(); Stack dirs = new Stack(); File startdir = new File(start); if ( startdir.isDirectory() ) dirs.push( new File(start) ); while ( dirs.size() > 0 ) { File dirFiles = (File) dirs.pop(); String s[] = dirFiles.list(); if ( s != null ) { for ( int i = 0; i < s.length; i++ ) { File file = new File( dirFiles.getAbsolutePath() + File.separator + s[i] ); if ( file.isDirectory() ) dirs.push( file ); else if ( match(s[i], extensions) ) files.addElement( file ); } } } } private static boolean match( String s1, String suffixes[] ) { for (int i = 0; i < suffixes.length; i++) if ( s1.length() >= suffixes[i].length() && s1.substring(s1.length() – suffixes[i].length(), s1.length()).equalsIgnoreCase(suffixes[i])) return true; return false; } private Vector files; }
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 aufgereite Sammlung von Schriftstücken.
3 Vor der Java SDK-Version 1.2 fehlte diese Möglichkeit. Dies machte sich bei Programmen zur Verzeichnisverwaltung, wie etwa einem Dateibrowser, nachteilig bemerkbar.
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.«
| << zurück |
Copyright © Galileo Press GmbH 2003
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.