Hallo, hab ein windows system, auf C:\ sind circa 100k Dateien, jetzt möchte ich nach einem genauen Dateinamen suchen, ist hierfür richtig: Pseudo:
if (f Verzeichnis)
File[] fa = f list
if (fa != null && fa.length != 0)
// als addAll toList fa
for i 0 i < fa.length i++
suche fa** s als // rekursiv Aufruf
else if (f getName equals s)
als add f
}```
Muss dazwischen noch .canRead()? file.listFiles() kann manchmal null zurückgeben. Der erste Tiefendurchlauf dauert bei mir ungefähr 40 Sekunden.
Grüße.
if (f.isDirectory()) {
File[] listFiles = f.listFiles();
if (listFiles != null && listFiles.length != 0) {
for (int i = 0; i < listFiles.length; i++) {
sucheNachDatei(s, listFiles**, alf);
}
}
} else if (f.getName().equals(s)) {
alf.add(f);
}
}```
Das Problem ist, dass da zimlic viele File[] und Files erstellt werden, außerdem viele Methodenaufrufe.
Beispiel: ``` ArrayList<File> alf = new ArrayList<File>();
sucheNachDatei(".picasa.ini", new File("C:\\"), alf);
sucheNachDatei(".picasa.ini", new File("D:\\"), alf);
sucheNachDatei(".picasa.ini", new File("E:\\"), alf);
for (int i = 0; i < alf.size(); i++) {
System.out.println(i + 1 + " = " + alf.get(i));
}```
Ungefähr 40 Sekunden, was lässt sich schneller schreiben? Wichtig ist halt, dass file.isDirectory() nicht langsamer ist als file.canRead(). Wenn ich das schneller habn möchte, muss ich wohl JNI implementieren oder?
[QUOTE=mymaksimus]Was erhoffst du dir durch canRead() fuer einen vorteil zu verschaffen?[/QUOTE]
Ich will gar keinen Vorteil habn, nur über Best practice diskutieren.
for-each- bewusst nicht gewählt (denn dann new Iterator<File>() ). Wieso gibt es den "Seiteneffekt", dass die zweite Suche nur noch 1/3 der Zeit benötigt? Naja, bis nachher.
Was passieren könnte (wäre eigentlich ein eher Unix-typisches Problem) ist das du auf bestimmten Verzeichnisse / Files keine Berechtigung hast. Nur ist Windows auch in den „großen“ Versionen wie Pro und Ulti selbst bei aktivem UAC und nem Gast-Konto recht großzügig was lese-Rechte angeht und man hat in der regel lediglich eine Beschränkungen auf die User-Verzeichnisse.
Ansonsten : das Problem mit dem direkt-rekursiven Call kenn ich : er geht halt erstmal bis runter in den tiefsten Ordner bevor er z.B. C:\ selbst vollständig abgearbeitet hat. Da müsste man sich das Command „tree“ mal genauer ansehen wie das läuft, weil das scheint ja etwas performanter.
— EDIT —
Würde ich mal spontan auf den Cache schieben, entweder von der Platte selbst oder irgendwo was (Windows-Spezifisch) im Explorer.
Wenn wir hier über Best-Practices diskutieren, dann darf Files.walkFileTree mit einem ensprechend ausprogrammierten FileVisitor nicht unerwähnt bleiben. Das ist imho der bei weitem eleganteste Weg, einen Dateibaum zu durch laufen (daher auch der Methodenname).
Problem, Problem. Mein JDK ist Java 6, noch nicht 7. Elegant vielleicht, aber es geht hier eigentlich um kleinste bisschen Performance. Travisator/-Pattern/-Interface/-abstrakte Klasse … klar, das gibt es. Außerdem geht’s um Alle-Dateien-Erreichen, die so erreicht werden können, ohne Wind. zu kennen.
Naja, Performance mit der Einschränkung auf Java6 … da dürfte es eher an den Binaries scheitern.
Zum “Plattformunabhängig” : jedes System chachet irgendwo was, und bei Unix-Filesystemen ist dies soweit ich es bemerken konnte noch ein Stück effizienter als NTFS, gerade was Directory-Listing angeht, aber mit Zugriffsrechten wirst du immet konfrontiert.
Erstmal gibts die Funktion File.listRoots() welche dir die vorhandenen Partitionen zurückgibt. Dann denke ich dass 40 sekunden ein ganz akzeptables Ergibnis für eine 3-fache Tiefensuche ist. Du wirst da mit JNI auch nix mehr rauskitzeln können, Java bedient sich da sicher schon an den OS-Funktionalitäten.
Was du machen könntest, wäre die Methode von rekursiv nach iterativ umzuschreiben - das spart ein Bisschen bei den Methodenaufrufen, ob das merkbar ist kann ich dir aber nicht versichern.
Das stimmt so leider nicht. File.listRoots() gibt lediglich die vom System zugreifbaren Dateisystemwurzeln zurück. Unter Unix ist das schlicht „/“, und Windows die vom System geladenen „Laufwerke“, wozu auch über SMB gemountete „Netzwerklaufwerke“ zählen. Da muss man halt ein wenig unterscheiden, könnte sonst leicht missverstanden werden.
Da würde ich dir so spontan mangels Kenntnis von C und den OS-APIs erstmal recht geben, aber ich würde doch denken das es sicherlich einen nativ-Call geben könnte mit dem man auf einen Cache zugreifen könnte, was ja offenbar eh schon passiert wenn man sich die Aussage von TO noch mal nimmt das es bei einem zweiten Durchlauf deutlich schneller geht.
Das wollte ich auch erst schreiben, aber wie soll man einen Baum beliebiger Tiefe iterativ durchlaufen ? Gut, ich hab mich mit Trees noch nicht beschäftigt, aber würde es nicht eine Verschachtelung von Schleifen bedeuten ? Ich mein : File.listFiles() gibt ein Array des Inhaltes des aktuellen Ordners zurück. Will ich nun dieses Array durchlaufen brauch ich entweder einen rekursiven Aufruf der gleichen Methode oder müsste entsprechend eine weitere innere Schleife nutzen. Oder hab ich da was Trees angeht völlig übersehen ?
Eh, du brauchst nur eine Datenstruktur, in der dann “quasi” eine Folge von Dateien/Files liegt (der sich verändernde Parameter), solange die DS nicht leer ist, fügst du neue Dateien hinzu oder entfernst wieder, also das, was ja beim Methodenaufruf auch passiert.
BFS (also der Breitendurchschreiten) wird das nur “zimlic” viel.
Also kurz gesagt, die <= 40 Sekunden reichen für die Suchvorgänge, die ich hab, aus. sind nicht zu viel. Unter ubuntu müsste ich das nachher noch ausprobieren. leider weiß ich nicht genau, was die windows suche eigentlich macht und die Suchergebnisse werden mir nicht vernünftig dargestellt, oder ich hab einfach keinr Lust, den Windows-Explorer zu verstehen. File.listRoots() das funktioniert schon, wenn man einige der “Files” (das sind ja virtuelle logische Laufwerke) wieder rausnimmt, sonst hat man die Netzwerklaufwerke mit drin (wie schon gesagt).
Ja, ich hab jetzt so nebenbei noch mal über die iterative Variante nachgedacht und bin dann auch auf die Idee einer List bzw eines FIFO-Stack gekommen. Von daher kann da noch mal die Frage gecanceld werden (ich sollte in zukunft ERST denken DANN posten).
Zur Windows-Explorer-Suche : der Explorer nutzt eine Indizierung, hat also irgendwo eine “Datenbank” in der halt indizierte Verzeichnisse regelmäßig aktualisiert werden so das bei einer Suchanfrage nur diese Datenbank und nicht das Dateisystem durchsucht werden muss. Wann genau allerdings diese re-indizierung (also das Überwachen der ausgewählten Verzeichnisse) passiert müsste irgendwo im MSDN zufinden sein.
Bzgl Unix-Filesysteme : auch NTFS ist ja ein journaled-FS, aber ich denke das Unix auf grund dieses Node-Systems gerade was directory-index-caching angeht einfach vom FS-level her ein stück optimierter ist als M$-NTFS und wie es verarbeitet wird, denn es kommt schon mal vor das wenn man im Explorer z.B. C:\Windows oder ähnliche aufruft dieser erstmal n bissl am rattern ist (also vermutlich den aktuellen Inhalt neu läd) was mir so unter Unix noch nie passiert ist, und das in Verzeichnissen die deutlich umfangreicher waren als C:\Windows oder C:\Windows\System32. Und auch wenn ich mal zum Test die rekursive Variante auf meinem Server laufen lasse habe ich zumindest subjektiv das Gefühl das es dort schneller läuft als hier auf meinem Desktop der mit einem RAID 5 SATA-3 eigentlich dem RAID 1 SATA-3 des Servers etwas überlegen sein sollte (zu mal auch der Rest des Systems deutlich mehr Gesamt-Leistung hat als die Server-Maschine). Aber wie schon erwähnt : ich denke nicht das man diese spezifischen Unterschiede so direkt miteinandner vergleichen kann.
Yo, aber ist das denn wirklich das Kernproblem? Falls du Cygwin installiert hast kannst du ja mal das time-Kommando bemühen. Bei mir schaut das beim ersten Aufruf so aus:
time ls -R /usr |wc -l
41432
real 0m14.266s
user 0m0.357s
sys 0m3.078s
Ein erneuter Aufruf lässt wohl darauf schließen, das die Daten der HD nun in irgendeinem Cache liegen:
real 0m2.844s
user 0m0.404s
sys 0m2.639s
Das eigentliche Programm braucht dabei augenscheinlich nicht viel Rechenzeit. Das größere Problem ist das Tempo der Datenträger und des Systems. Und da kann man nicht viel dran machen (ausser vielleicht mal defragmentieren).
[QUOTE=CyborgBeta;104434]
Beispiel: ArrayList<File> alf = new ArrayList<File>(); sucheNachDatei(".picasa.ini", new File("C:\\"), alf); sucheNachDatei(".picasa.ini", new File("D:\\"), alf); sucheNachDatei(".picasa.ini", new File("E:\\"), alf); for (int i = 0; i < alf.size(); i++) { System.out.println(i + 1 + " = " + alf.get(i)); }
Ungefähr 40 Sekunden, was lässt sich schneller schreiben?[/quote]
Man könnte wohl sucheNachDatei als Thread formulieren und alle Suchen parallel laufen lassen. Das dürfte zumindest nicht wesentlich langsamer sein.
Hmmm, best Practice hin oder her, ich würde die Suche zunächst mal als Strategie formulieren. Und dann verschiedene Strategien implementieren, solange die Laune reicht…
locate-db anzapfen (kann’s zumindest bei Unixen geben, bei Windows gibt’s bestimmt was vergleichbares; ansonsten wären auch die Datenbanken dieser Desktop-Suchmaschinen oder wie die heissen denkbar)
Direktzugriff auf den Datenträger und sich selber das Dateisystem entlanghangeln
das native dir/ls/locate/find/wasauchimmer-Kommando starten und dessen Ausgabe abfangen
…
Ansonsten würde ich persönlich mir da ehrlich gesagr nicht allzuviele Gedanken über Best-Practice machen. Das Traversieren von Verzeichnissen ist eine Sache von - keine Ahnung, vielleicht 30 Zeilen Sourcecode? Die kann man auch noch überblicken wenn sie nicht nach dem Lehrbuch gestaltet sind. Und wesentlich schneller dürfte eine Lehrbuchmethode auch net sein.
Hier nochmal meinen Senf:
Voellig egal was du machst, es hat keinen Einfluss auf die Geschwindigkeit.
Die liegt naemlich zu 99,99% beim OS, caching etc wurden ja schon genannt.
Weder die JVM noch eine ander Plattform machen den Zugriff auf das Dateisystem selbst, das geht immer uebers OS (war schon bei CP/M so, damals eben findfirst/findnext), sonst muesste die JVM ja das FAT, NTFS, extfs etc. pp. selber implementieren und danach trotzdem mit dem OS synchronisieren.
Best Practice waere es, sowas so sauber wie noetig zu implementieren, d.h. je nach nutzen kann man da sogar mit Composite und Visitor Patterns arbeiten.
Nebenbei, Arrays in Arrays ist eine Form von Baum, wobei man das in einer OO Sprache besser darstellen sollte
Arrays sind dazu das falsche Mittel.
Das einzige was mir noch einfällt sind Bloomfilter. Damit kann man das ganze FS indexieren und zu einem möglichst hohen Prozentsatz die Aussage treffen, dass eine bestimmte Datei zum Zeitpunkt der Indexierung nicht vorhanden war.
Die Aussage ist schnell und die Indexierung speicherarm.
Um ein bisschen Sarkasmus reinzubringen und eine sehr bekannte Phrase zu rezitieren : “just in case you didn’t noticed” : das mit der Indizierung hatte ich bereits bei der Win-Explorer-Suche angesprochen. Und auch hatte ich bereits die Vermutung geäußert das ein solcher “directory-index-cache” oder ähnliches bereits auf FileSystem-Ebene ansetzt. Aber trotzdem danken wir dir das du es noch mal so hervorgehoben hast. [ironie off]
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package pkg29datei.en.suchen;
import java.io.File;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;
/**
* @author CB
*/
public class EnSuchen {
public static void sucheNachDatei(String s, File f, ArrayList<File> alf) {
if (f.getName().contains(s)) {
alf.add(f);
}
if (f.isDirectory()) {
File[] listFiles = f.listFiles();
if (listFiles != null && listFiles.length != 0) {
for (int i = 0; i < listFiles.length; i++) {
sucheNachDatei(s, listFiles**, alf);
}
}
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
String s = JOptionPane.showInputDialog("Zu suchende/s Datei/Verzeichnis:");
if (s == null || s.isEmpty()) {
return;
}
ArrayList<File> alf = new ArrayList<File>();
long t1 = System.currentTimeMillis();
sucheNachDatei(s, new File("C:\\"), alf);
sucheNachDatei(s, new File("D:\\"), alf);
sucheNachDatei(s, new File("E:\\"), alf);
long t2 = System.currentTimeMillis();
JTextArea jta = new JTextArea(String.format("Vorhanden ( %d ms ):%n", t2 - t1));
for (File file : alf) {
jta.append(file.toString());
jta.append("
");
}
JFrame jf = new JFrame("29Datei(en)Suchen");
jf.add(new JScrollPane(jta));
jf.pack();
jf.setSize(800, 600);
jf.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jf.setVisible(true);
}
}```
Ihr könnt ja meckern, wenn etwas falsch ist, oder es auch verwenden.
Als nächster Schritt sollte jetzt rein, 2 Interfaces Durchschreiten und nextToken oder eine abstrakte Klasse, die Durchschreiten schon implementiert, nextToken aber nicht. nextToken kann entweder aufgerufen oder übergeben werden, je nach design.
An .isDirectory() und .listFiles() kann man nichts verändern, aber `if (f.getName().contains(s)) { alf.add(f); }` könnte man natürlich selber und schneller/anders schreiben.
Schönes Wochenene.