Advanced Java Services | DirectoryStream, PathMatcher |
Ab Java 7 gibt es in Files die überladene statische Methode newDirectoryStream() welche die Methoden list() und listFiles() aus der Klasse File ersetzt. Das Interface DirectoryStream<T> implementiert das Interface Iterable und ist hier vom generischen Typ Path. Es gibt also eine Methode iterator die einen Iterator vom generischen Typ Path liefert. Dieser Iterator unterstützt aber keine remove() Operation. Auf Windowssystemen liefert newDirectoryStream(Path dir) ein Objekt vom Typ sun.nio.fs.WindowsDirectoryStream.
DirectoryStream ist recht komfortabel zu verwenden. Da er das Interface AutoCloseable implementiert bietet sich die Verwendung von try-with-resources an. Die Methode tritt in drei verschiedenen Ausprägungen auf.
Returntyp | Methode |
---|---|
static DirectoryStream<Path> | newDirectoryStream(Path dir) Opens a directory, returning a DirectoryStream to iterate over all entries in the directory. |
static DirectoryStream<Path> | newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) Opens a directory, returning a DirectoryStream to iterate over the entries in the directory. |
static DirectoryStream<Path> | newDirectoryStream(Path dir, String glob) Opens a directory, returning a DirectoryStream to iterate over the entries in the directory. |
Nachfolgend einige Beispiele.
So werden alle Dateien im übergebenen Directory aufgelistet. Man sieht, daß die Methode nicht rekursiv arbeitet. Außerdem werden die Namen der Unterverzeichnisse nicht gelistet.
Path dir = Paths.get("c:/foo"); try(DirectoryStream<Path> dirStream = Files.newDirectoryStream(dir)) // liefert ein Iterable<Path> { for(Path path : dirStream) { System.out.println(path); // liefert absolute Pfade //System.out.println(path.getFileName()); // liefert nur den Dateinamen } } catch(IOException ex) { System.out.println(ex); }
In der Überladung der Form newDirectoryStream(Path dir, String glob) kann man den zweiten Parameter dazu benutzen einfache Filter anzugeben. Durch das folgende CodeSchnipsel werden alle Dateien aufgelistet, welche die Dateiendungen .c, .h, .cpp oder .java haben. Die Namen der Unterverzeichnisse werden nicht aufgelistet.
try(DirectoryStream<Path> dirStream = Files.newDirectoryStream(dir, "*.{c,h,cpp,java}")) { for(Path path : dirStream) { System.out.println(path); } } catch(IOException ex) { System.out.println(ex); }
Die dritte Überladung der Methode newDirectoryStream() erlaubt die Verwendung eigener Filter. Ein selbstgeschriebener Filter muß das Interface DirectoryStream.Filter<T> implementieren, also ein statisches inneres Interface von DirectoryStream.
Hier zwei Beispiele.
Der Filter sieht folgendermaßen aus.
/* * Ein Filter, dem man eine Dateigröße übergeben kann. Angezeigt * werden die Dateien, die größer sind als die übergebene Grenze. */ class SizeFilter implements DirectoryStream.Filter<Path> { private int lowerBound; public SizeFilter(int lowerBound) { this.lowerBound = lowerBound; } @Override public boolean accept(Path entry) throws IOException { return Files.size(entry) > lowerBound; } }
Wenn man den Filter geschrieben hat, übergibt man ihn einfach der passenden Überladung von newDirectoryStream(). Die Mindestdateigröße wird im Konstruktor übergeben.
Path dir = Paths.get("c:/tmp"); SizeFilter sizeFilter = new SizeFilter(3000*1024); try(DirectoryStream<Path> dirStream = Files.newDirectoryStream(dir, sizeFilter)) // liefert ein Iterable{ for(Path path : dirStream) { System.out.println(path + " " + Files.size(path)); } } catch(IOException ex) { System.out.println(ex); ex.printStackTrace(); }
Der Quellcode der Methode DirectoryStream<Path> newDirectoryStream(Path dir, String glob) sieht wie folgt aus.
public static DirectoryStream<Path> newDirectoryStream(Path dir, String glob) throws IOException { // avoid creating a matcher if all entries are required. if (glob.equals("*")) return newDirectoryStream(dir); // create a matcher and return a filter that uses it. FileSystem fs = dir.getFileSystem(); final PathMatcher matcher = fs.getPathMatcher("glob:" + glob); DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter() { @Override public boolean accept(Path entry) { return matcher.matches(entry.getFileName()); } }; return fs.provider().newDirectoryStream(dir, filter); }
Diesen Ansatz kopiert der folgende Filter.
/* * Ein Filter, der die Namen der Unterverzeichnisse anzeigt. Über den Konstruktor kann zusätzlich * ein String mit einem Pattern übergeben werden, das nach denselben Regeln aufgebaut sein muß * wie in newDirectoryStream(Path dir, String glob). Falls im Konstruktor nur das Ausgangsverzeichnis * angegeben wird, werden nur die Unterverzeichnisse angezeigt. */ class DirFilter implements DirectoryStream.Filter<Path> { private String glob; private Path dir; /* * zeigt nur die SubDirs an */ public DirFilter(Path dir) { this(dir, null); } /* * Zeigt die SubDirs an und die Dateien die dem Pattern entsprechen */ public DirFilter(Path dir, String glob) { this.glob = glob; this.dir = dir; } @Override public boolean accept(Path entry) throws IOException { if (Files.isDirectory(entry)) return true; if (glob != null && !glob.equals("")) { // create a matcher and return a filter that uses it. FileSystem fs = dir.getFileSystem(); PathMatcher matcher = fs.getPathMatcher("glob:" + glob); return matcher.matches(entry.getFileName()); } return false; } }
Und so kann dieser Filter verwendet werden.
DirFilter dirFilter = new DirFilter(dir, "*.{c,h,cpp,java}"); try(DirectoryStreamdirStream = Files.newDirectoryStream(dir, dirFilter)) { for(Path path : dirStream) { System.out.print(path); if (Files.isDirectory(path)) System.out.println(" <dir>"); if (Files.isRegularFile(path)) System.out.println(" <file>"); } } catch(IOException ex) { System.out.println(ex); }