Zählen der Anzahl der Dateien in einem Verzeichnis mit Java

Wie zähle ich die Anzahl der Dateien in einem Verzeichnis mithilfe von Java? Nehmen wir zur Vereinfachung an, dass das Verzeichnis keine Unterverzeichnisse hat.

Ich kenne die Standardmethode von:

new File().listFiles().length 

Dies wird jedoch effektiv alle Dateien im Verzeichnis durchlaufen, was bei einer großen Anzahl von Dateien lange dauern kann. Außerdem sind mir die eigentlichen Dateien im Verzeichnis egal, es sei denn, ihre Nummer ist größer als eine bestimmte große Zahl (etwa 5000).

Ich vermute, aber speichert das Verzeichnis (oder sein i-Knoten im Falle von Unix) nicht die Anzahl der enthaltenen Dateien? Wenn ich diese Nummer direkt vom Dateisystem bekommen könnte, wäre das viel schneller. Ich muss diese Überprüfung für jede HTTP-Anforderung auf einem Tomcat-Server durchführen, bevor das Back-End mit der eigentlichen Verarbeitung beginnt. Daher ist Geschwindigkeit von größter Wichtigkeit.

Ich könnte ab und zu einen Daemon ausführen, um das Verzeichnis zu löschen. Ich weiß das, also bitte geben Sie mir diese Lösung nicht.

Dies ist möglicherweise nicht für Ihre Anwendung geeignet, aber Sie könnten immer einen nativen Anruf (mit jni oder jna ) versuchen oder einen plattformspezifischen Befehl ausführen und die Ausgabe lesen, bevor Sie in list (). Length zurückfallen. On * nix, du ls -1a | wc -l ls -1a | wc -l (Notiz – das ist Strich-Eins-a für den ersten Befehl, und Strich-Kleinbuchstaben-L für die Sekunde). Nicht sicher, was genau an Windows wäre – vielleicht nur ein Richtungsweiser und suche nach der Zusammenfassung.

Bevor Sie sich mit so etwas beschäftigen, empfehle ich Ihnen, ein Verzeichnis mit einer sehr großen Anzahl von Dateien zu erstellen und nur zu sehen, ob list (). Length wirklich wirklich zu lange dauert. Wie dieser Blogger vorschlägt, möchten Sie vielleicht nicht schwitzen.

Ich würde wahrscheinlich mit Varchans Antwort selbst gehen.

Ah … die Gründe dafür, dass man in Java keine einfache Methode dafür hat, ist die Dateispeicherabstraktion: Bei einigen Dateisystemen ist die Anzahl der Dateien in einem Verzeichnis nicht immer verfügbar … diese Zählung hat vielleicht überhaupt keine Bedeutung ( siehe zum Beispiel verteilte, P2P-Dateisysteme, fs, die Dateilisten als verknüpfte Liste speichern, oder datenbankgestützte Dateisysteme …). Also ja,

 new File().list().length 

ist wahrscheinlich deine beste Wette.

Seit Java 8 können Sie das in drei Zeilen tun:

 try (Stream files = Files.list(Paths.get("your/path/here"))) { long count = files.count(); } 

Zu den 5000 Kindknoten und Inode-Aspekten:

Diese Methode wird über die Einträge iterieren, aber wie Varkhan vorgeschlagen hat, können Sie wahrscheinlich nicht besser sein, als mit JNI oder direkten Systembefehlen zu spielen, aber selbst dann können Sie nie sicher sein, dass diese Methoden nicht dasselbe tun!

Aber lassen Sie uns ein wenig darüber nachdenken:

Wenn Sie die JDK8-Quelle betrachten, stellt Files.list einen Stream Files.list , der ein Iterable aus Files.newDirectoryStream , das an FileSystemProvider.newDirectoryStream delegiert.

Auf UNIX-Systemen (dekompilierte sun.nio.fs.UnixFileSystemProvider.class ) wird ein Iterator sun.nio.fs.UnixSecureDirectoryStream : Ein sun.nio.fs.UnixSecureDirectoryStream wird verwendet (mit Dateisperren während der Iteration durch das Verzeichnis).

Es gibt also einen Iterator, der die Einträge hier durchläuft.

Schauen wir uns jetzt den Zählmechanismus an.

Die tatsächliche Zählung wird durch die von Java-8-Streams bereitgestellte zähl- / summenreduzierende API durchgeführt. Theoretisch kann diese API ohne viel Aufwand (mit Multi-Threading) parallele Operationen durchführen. Der Stream wird jedoch mit deaktivierter Parallelität erstellt, daher ist es ein Nein.

Die gute Seite dieses Ansatzes besteht darin, dass das Array nicht im Speicher geladen wird, da die Einträge von einem Iterator gezählt werden, wenn sie von der zugrunde liegenden API (Dateisystem) gelesen werden.

Schließlich muss ein Verzeichnisknoten für die Information konzeptionell in einem Dateisystem nicht die Anzahl der darin enthaltenen Dateien enthalten, sondern kann nur die Liste seiner untergeordneten Knoten (Liste der Inodes) enthalten. Ich bin kein Experte für Dateisysteme, aber ich glaube, dass UNIX-Dateisysteme genau so funktionieren. Sie können also nicht davon ausgehen, dass es eine Möglichkeit gibt, diese Information direkt zu haben (dh es kann immer irgendwo eine Liste von Kindknoten geben, die irgendwo versteckt sind).

Leider glaube ich, dass das schon der beste Weg ist (obwohl list() etwas besser ist als listFiles() , da es keine listFiles() ).

Da Sie die Gesamtanzahl nicht wirklich benötigen und tatsächlich eine Aktion nach einer bestimmten Anzahl (in Ihrem Fall 5000) ausführen möchten, können Sie java.nio.file.Files.newDirectoryStream . Der Vorteil ist, dass Sie früher beenden können, anstatt das gesamte Verzeichnis durchzugehen, nur um eine Zählung zu erhalten.

 public boolean isOverMax(){ Path dir = Paths.get("C:/foo/bar"); int i = 1; try (DirectoryStream stream = Files.newDirectoryStream(dir)) { for (Path p : stream) { //larger than max files, exit if (++i > MAX_FILES) { return true; } } } catch (IOException ex) { ex.printStackTrace(); } return false; } 

Das Interface doc für DirectoryStream hat auch einige gute Beispiele.

Wenn Sie Verzeichnisse haben, die wirklich (> 100’000) viele Dateien enthalten, gibt es hier einen (nicht portablen) Weg:

 String directoryPath = "a path"; // -f flag is important, because this way ls does not sort it output, // which is way faster String[] params = { "/bin/sh", "-c", "ls -f " + directoryPath + " | wc -l" }; Process process = Runtime.getRuntime().exec(params); BufferedReader reader = new BufferedReader(new InputStreamReader( process.getInputStream())); String fileCount = reader.readLine().trim() - 2; // accounting for .. and . reader.close(); System.out.println(fileCount); 

Die Verwendung von Sigar sollte helfen. Sigar hat native Hooks, um die Statistiken zu erhalten

 new Sigar().getDirStat(dir).getTotal() 

Leider ist File.list (), wie mmyer sagt, ungefähr so ​​schnell, wie Sie es mit Java bekommen. Wenn die Geschwindigkeit so wichtig ist, wie Sie sagen, sollten Sie diese spezielle Operation mit JNI in Erwägung ziehen. Sie können dann Ihren Code an Ihre spezielle Situation und Ihr Dateisystem anpassen.

 public void shouldGetTotalFilesCount() { Integer reduce = of(listRoots()).parallel().map(this::getFilesCount).reduce(0, ((a, b) -> a + b)); } private int getFilesCount(File directory) { File[] files = directory.listFiles(); return Objects.isNull(files) ? 1 : Stream.of(files) .parallel() .reduce(0, (Integer acc, File p) -> acc + getFilesCount(p), (a, b) -> a + b); }