Gibt es eine Abhilfe für Java schlechte Leistung beim Gehen große Verzeichnisse?

stimmen
16

Ich versuche, Dateien einer nach dem anderen zu verarbeiten, die über ein Netzwerk gespeichert sind. Lesen der Dateien ist schnell durch Pufferung ist nicht das Problem. Das Problem ich habe, ist die Auflistung nur die Verzeichnisse in einem Ordner. Ich habe mindestens 10k Dateien pro Ordner über viele Ordner.

Die Leistung ist super langsam, da File.list () gibt ein Array anstelle eines iterable. Java geht aus und sammelt alle Namen in einem Ordner und packt sie in ein Array vor der Rückkehr.

Der Bug - Eintrag für diese ist http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834 und verfügt nicht über eine Arbeit um. Sie sagen , gerade diese für JDK7 wurde behoben.

Ein paar Fragen:

  1. Hat jemand auf diese Performance-Engpass eine Abhilfe?
  2. Ich versuche das Unmögliche zu erreichen? Wird die Leistung immer noch arm sein, auch wenn es nur iteriert über die Verzeichnisse?
  3. Kann ich die Beta-JDK7 baut verwenden, der diese Funktionalität hat, ohne dass mein gesamtes Projekt auf ihn bauen?
Veröffentlicht am 10/12/2008 um 01:14
quelle vom benutzer
In anderen Sprachen...                            


10 antworten

stimmen
7

Auch wenn es nicht schön ist, löste ich diese Art von Problem, wenn durch die Ausgabe von dir / ls in eine Datei kochend vor meiner App starten und in den Dateinamen übergeben.

Wenn Sie es innerhalb der Anwendung tun musste, konnte man nur verwenden system.exec (), aber es würde einige Gemeinheiten erstellen.

Du hast gefragt. Die erste Form geht unglaublich schnell sein, sollte die zweite als auch ziemlich schnell sein.

Achten Sie darauf, die ein Element pro Zeile (blank, keine Dekoration, keine Grafiken), vollständigen Pfad und recurse Optionen des ausgewählten Befehls zu tun.

BEARBEITEN:

30 Minuten nur um eine Verzeichnisliste zu bekommen, wow.

Es fiel mir nur, dass, wenn Sie exec () verwenden, können Sie bekommen es stdout in ein Rohr umgeleitet, statt es in eine Datei zu schreiben.

Wenn Sie das getan haben, sollen Sie beginnen, die Dateien sofort bekommen und in der Lage sein, die Verarbeitung zu beginnen, bevor der Befehl abgeschlossen hat.

Die Wechselwirkung kann tatsächlich Dinge verlangsamen, aber vielleicht nicht - man könnte es versuchen.

Wow, ich ging einfach für Sie die Syntax des Befehls .exec zu finden und kam über diese, möglicherweise genau das, was Sie wollen (es enthält ein Verzeichnis exec und „ls“ und Rohre das Ergebnis in Ihr Programm für die Verarbeitung): gute Verbindung in wayback (Jörg , die in einem Kommentar zu ersetzen diese ein von der Sonne , die Oracle brach)

Wie dem auch sei, die Idee ist einfach, aber den Code immer richtig ist ärgerlich. Ich werde ein paar Codes aus den internets gehen stehlen und hacken sie auf - brb


/ **
 * Hinweis: Nur als letztes Mittel verwenden Sie diese! Es ist spezifisch für Fenster und sogar
 * An, dass es keine gute Lösung, aber es sollte schnell sein.
 * 
 * Es zu benutzen, erweitern FileProcessor und rufen processFiles ( „...“) mit einer Liste
 wenn man sie wie / s * von Optionen wollen ... Ich empfehle / b
 * 
 * Außer Kraft setzt process und es wird einmal für jede Zeile der Ausgabe aufgerufen werden.
 * /
import java.io. *;

public abstract class FileProcessor
{
   public void processFiles (String dirOptions)
   {
      Der Prozeß verarbeitet = null;
      BufferedReader inStream = null;

      // Die Hallo-Klasse aufrufen
      Versuchen
      {
          . Der Prozeß = Runtime.getRuntime (), exec ( "cmd / c dir" + dirOptions);
      }
      catch (IOException e)
      {
         System.err.println ( "Fehler auf exec () Methode");
         e.printStackTrace ();  
      }

      // vom gerufenen Programm Standardausgabe lesen
      Versuchen
      {
         inStream = new BufferedReader (
                                neue Input (theProcess.getInputStream ()));  
         process (inStream.readLine ());
      }
      catch (IOException e)
      {
         System.err.println ( "Fehler auf inStream.readLine ()");
         e.printStackTrace ();  
      }

   } // Ende Methode
   / ** diese Methode außer Kraft setzen - einmal für jede Datei * aufgerufen wird /
   public void process (String filename);


} // end class

Und danke Code Spender bei IBM

Beantwortet am 10/12/2008 um 01:57
quelle vom benutzer

stimmen
4

Eine Alternative ist, die Dateien zu haben, ein anderes Protokoll dienten über. Wie ich verstehe Sie SMB dafür verwenden und Java versucht wird, nur um sie als reguläre Datei aufzulisten.

Das Problem ist hier vielleicht nicht allein sein Java (wie funktioniert es sich verhält, wenn Sie das Verzeichnis mit Microsoft Explorer öffnen x: \ shared) Nach meiner Erfahrung auch eine erheblich Menge Zeit in Anspruch nehmen.

Sie können das Protokoll so etwas wie HTTP ändern, nur die Dateinamen zu holen. Auf diese Weise können Sie die Liste der Dateien über HTTP abzurufen (10k Zeilen should't zu viel) und den Server handelt von Dateiliste lassen. Dies würde sehr schnell sein, da sie mit dem lokalen Ressourcen (die in dem Server) ausgeführt werden

Dann, wenn Sie die Liste haben, können Sie sie eins nach dem genau die Art und Weise verarbeiten Sie gerade tun.

Der Schlüsselpunkt ist einen Hilfsmechanismus, der in der anderen Seite des Knotens zu haben.

Ist das machbar?

Heute:

File [] content = new File("X:\\remote\\dir").listFiles();

for ( File f : content ) {
    process( f );
}

vorgeschlagen:

String [] content = fetchViaHttpTheListNameOf("x:\\remote\\dir");

for ( String fileName : content ) {
    process( new File( fileName ) );
}

Der HTTP-Server konnte eine sehr kleine kleine und einfache Datei sein.

Ist dies so, wie Sie es gerade jetzt, was Sie tun, ist es, alle 10k-Dateien Informationen zu Ihrem Client-Rechner zu holen (ich weiß nicht, wie viel von dieser Information), wenn Sie nur den Dateinamen für die spätere Verarbeitung benötigen .

Wenn die Verarbeitung gerade jetzt sehr schnell ist, kann es ein wenig verlangsamt werden. Dies liegt daran, die Prefetch-Informationen ist nicht mehr verfügbar.

Versuche es.

Beantwortet am 10/12/2008 um 01:36
quelle vom benutzer

stimmen
3

Wie wäre es File.list (Filenamefilter) Methode und Umsetzung FilenameFilter.accept (File dir, String name), um jede Datei zu verarbeiten und false zurück.

Ich lief dies auf Linux vm für Verzeichnis mit 10K + Dateien und es dauerte <10 Sekunden.

import java.io.File;  
import java.io.FilenameFilter;

public class Temp {
    private static void processFile(File dir, String name) {
        File file = new File(dir, name);
        System.out.println("processing file " + file.getName());
    }

    private static void forEachFile(File dir) {
        String [] ignore = dir.list(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                processFile(dir, name);
                return false;
            }
        });
    }

    public static void main(String[] args) {
        long before, after;
        File dot = new File(".");
        before = System.currentTimeMillis();
        forEachFile(dot);
        after = System.currentTimeMillis();
        System.out.println("after call, delta is " + (after - before));
    }  
}
Beantwortet am 22/10/2013 um 15:20
quelle vom benutzer

stimmen
3

Ich bezweifle, das Problem ist, an den Fehlerbericht beziehen Sie verwiesen. Das Problem gibt es „nur“ Speichernutzung, aber nicht unbedingt beschleunigen. Wenn Sie den Fehler über genügend Speicher ist für Ihr Problem nicht relevant.

Sie sollten Ihr Problem ist , speicherbezogene oder nicht messen , ob. Schalten Sie den Garbage Collector - Protokoll und verwenden zum Beispiel gcviewer auf die Speichernutzung zu analysieren.

Ich vermute, dass es mit dem SMB-Protokoll zu tun hat, das Problem verursacht. Sie können versuchen, einen Test in einer anderen Sprache zu schreiben und sehen, ob es schneller, oder Sie können versuchen, die Liste der Dateinamen durch ein anderes Verfahren zu bekommen, wie in einem anderen Beitrag hier beschrieben.

Beantwortet am 10/12/2008 um 09:49
quelle vom benutzer

stimmen
2

Eine nicht-portable Lösung wäre nativen Anrufe an das Betriebssystem zu machen und die Ergebnisse streamen.

für Linux

Sie können so etwas wie aussehen readdir . Sie können die Verzeichnisstruktur wie eine verknüpfte Liste gehen und zurückgehen in den Reihen oder einzeln.

Für Windows

Verwendung in Fenstern würde das Verhalten ziemlich ähnlich sein Findfirstfile und Findnextfile apis.

Beantwortet am 10/12/2008 um 01:22
quelle vom benutzer

stimmen
1

Wenn Sie auf Java 1.5 oder 1.6 sind, Beschuss aus „dir“ Befehlen und die Standardausgabe auf Windows-Parsing ist ein durchaus akzeptabel Ansatz. Ich habe diesen Ansatz in der Vergangenheit für die Verarbeitung von Netzlaufwerken verwendet, und es hat in der Regel viel schneller als wartet auf den nativen java.io.File Listfiles () -Methode zurück gewesen.

Natürlich sollte ein JNI Aufruf schneller sein und potenziell sicherer als „dir“ Befehle Beschuss aus. Die folgende JNI-Code kann verwendet werden, um eine Liste der Dateien / Verzeichnisse API mit dem Windows abzurufen. Diese Funktion kann leicht in eine neue Klasse Refactoring werden, damit der Anrufer inkrementell Dateipfade abgerufen werden kann (dh einen Pfad zu einem Zeitpunkt erhalten). Zum Beispiel können Sie den Code Refactoring, so dass FindFirstFileW in einem Konstruktor und haben eine separate Methode aufrufen FindNextFileW genannt wird.

JNIEXPORT jstring JNICALL Java_javaxt_io_File_GetFiles(JNIEnv *env, jclass, jstring directory)
{
    HANDLE hFind;
    try {

      //Convert jstring to wstring
        const jchar *_directory = env->GetStringChars(directory, 0);
        jsize x = env->GetStringLength(directory);
        wstring path;  //L"C:\\temp\\*";
        path.assign(_directory, _directory + x);
        env->ReleaseStringChars(directory, _directory);

        if (x<2){
            jclass exceptionClass = env->FindClass("java/lang/Exception");
            env->ThrowNew(exceptionClass, "Invalid path, less than 2 characters long.");
        }

        wstringstream ss;
        BOOL bContinue = TRUE;
        WIN32_FIND_DATAW data;
        hFind = FindFirstFileW(path.c_str(), &data);
        if (INVALID_HANDLE_VALUE == hFind){
            jclass exceptionClass = env->FindClass("java/lang/Exception");
            env->ThrowNew(exceptionClass, "FindFirstFileW returned invalid handle.");
        }


        //HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
        //DWORD dwBytesWritten;


        // If we have no error, loop thru the files in this dir
        while (hFind && bContinue){

          /*
          //Debug Print Statment. DO NOT DELETE! cout and wcout do not print unicode correctly.
            WriteConsole(hStdOut, data.cFileName, (DWORD)_tcslen(data.cFileName), &dwBytesWritten, NULL);
            WriteConsole(hStdOut, L"\n", 1, &dwBytesWritten, NULL);
            */

          //Check if this entry is a directory
            if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
                // Make sure this dir is not . or ..
                if (wstring(data.cFileName) != L"." &&
                    wstring(data.cFileName) != L"..")
                {   
                    ss << wstring(data.cFileName) << L"\\" << L"\n";
                }
            }
            else{
                ss << wstring(data.cFileName) << L"\n";
            }
            bContinue = FindNextFileW(hFind, &data);
        }   
        FindClose(hFind); // Free the dir structure



        wstring cstr = ss.str();
        int len = cstr.size();
        //WriteConsole(hStdOut, cstr.c_str(), len, &dwBytesWritten, NULL);
        //WriteConsole(hStdOut, L"\n", 1, &dwBytesWritten, NULL);
        jchar* raw = new jchar[len];
        memcpy(raw, cstr.c_str(), len*sizeof(wchar_t));
        jstring result = env->NewString(raw, len);
        delete[] raw;
        return result;
    }
    catch(...){
        FindClose(hFind);
        jclass exceptionClass = env->FindClass("java/lang/Exception");
        env->ThrowNew(exceptionClass, "Exception occured.");
    }

    return NULL;
}

Credit: https://sites.google.com/site/jozsefbekes/Home/windows-programming/miscellaneous-functions

Auch bei diesem Ansatz gibt es immer noch die Effizienz gewonnen werden. Wenn Sie den Pfad zu einem java.io.File serialisiert, gibt es eine großen Leistungseinbußen - vor allem, wenn der Pfad auf einem Netzlaufwerk eine Datei darstellt. Ich habe keine Ahnung, was Sun / Oracle ist unter der Haube zu tun, aber wenn Sie zusätzliche Datei benötigen Attribute außer dem Dateipfad (zB Größe, mod Datum, etc.), habe ich festgestellt, dass die folgende JNI-Funktion ist viel schneller als ein Java-Instanziierung .io.File den Pfad auf einem Netzobjekt.

JNIEXPORT jlongArray JNICALL Java_javaxt_io_File_GetFileAttributesEx(JNIEnv *env, jclass, jstring filename)
{   

  //Convert jstring to wstring
    const jchar *_filename = env->GetStringChars(filename, 0);
    jsize len = env->GetStringLength(filename);
    wstring path;
    path.assign(_filename, _filename + len);
    env->ReleaseStringChars(filename, _filename);


  //Get attributes
    WIN32_FILE_ATTRIBUTE_DATA fileAttrs;
    BOOL result = GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &fileAttrs);
    if (!result) {
        jclass exceptionClass = env->FindClass("java/lang/Exception");
        env->ThrowNew(exceptionClass, "Exception Occurred");
    }

  //Create an array to store the WIN32_FILE_ATTRIBUTE_DATA
    jlong buffer[6];
    buffer[0] = fileAttrs.dwFileAttributes;
    buffer[1] = date2int(fileAttrs.ftCreationTime);
    buffer[2] = date2int(fileAttrs.ftLastAccessTime);
    buffer[3] = date2int(fileAttrs.ftLastWriteTime);
    buffer[4] = fileAttrs.nFileSizeHigh;
    buffer[5] = fileAttrs.nFileSizeLow;

    jlongArray jLongArray = env->NewLongArray(6);
    env->SetLongArrayRegion(jLongArray, 0, 6, buffer);
    return jLongArray;
}

Sie können ein voll funktionierendes Beispiel dieses JNI-basierten Ansatzes in der finden javaxt-Core - Bibliothek. In meinen Tests mit Java 1.6.0_38 mit einem Windows - Host eine Windows - Freigabe schlägt, habe ich diesen JNI Ansatz etwa 10x schneller gefunden dann java.io.File Listfiles () aufrufen oder Beschuss aus „dir“ Befehle.

Beantwortet am 27/01/2013 um 18:30
quelle vom benutzer

stimmen
1

Wenn Sie schließlich müssen verarbeitet alle Dateien, dann Iterable mit über String [] werden Ihnen keinen Vorteil geben, wie Sie immer noch die ganze Liste von Dateien gehen und holen werden müssen.

Beantwortet am 10/12/2008 um 13:26
quelle vom benutzer

stimmen
0

Sind Sie sicher, dass es auf Java, nicht nur ein generelles Problem mit mit 10k Einträgen in einem Verzeichnis fällig, vor allem über das Netzwerk?

Haben Sie versuchen, ein Proof-of-Concept-Programm schreiben, das gleiche in C mit dem win32 Findfirst / Findnext-Funktionen zu tun, um zu sehen, ob es nicht schneller?

Ich weiß nicht, die Ins und Outs der SMB, aber ich vermute stark, dass es eine Rundfahrt für jede Datei in der Liste muss - was nicht schnell sein wird, insbesondere über ein Netzwerk mit mäßiger Latenz.

10k-Strings in einem Array mit klingt wie etwas, das den modernen Java-VM zu viel entweder nicht von der Steuer sollte.

Beantwortet am 01/03/2009 um 09:06
quelle vom benutzer

stimmen
0

eine Iterable Mit bedeutet nicht, dass die Dateien werden Sie gestreamt werden. In der Tat seine in der Regel das Gegenteil. So eine Anordnung ist in der Regel schneller als ein Iterable.

Beantwortet am 01/03/2009 um 08:56
quelle vom benutzer

stimmen
0

Ich frage mich, warum es 10k-Dateien in einem Verzeichnis. Einige Dateisysteme funktionieren nicht gut mit so vielen Dateien. Es gibt Besonderheiten Beschränkungen für Dateisysteme wie maximaler Anzahl von Dateien pro Verzeichnis und maximale Anzahl von Ebenen des Unterverzeichnisses.

Ich löse ein ähnliches Problem mit einer Iterator Lösung.

Ich brauchte über riesige directorys und mehrere Ebenen der Verzeichnisbaum rekursiv zu gehen.

Ich versuche FileUtils.iterateFiles () von Apache commons io. Aber es den Iterator implementieren, indem Sie alle Dateien in einer Liste hinzufügen und dann List.iterator Rückkehr (). Es ist sehr schlecht für das Gedächtnis.

Also ziehe ich es um so etwas zu schreiben:

private static class SequentialIterator implements Iterator<File> {
    private DirectoryStack dir = null;
    private File current = null;
    private long limit;
    private FileFilter filter = null;

    public SequentialIterator(String path, long limit, FileFilter ff) {
        current = new File(path);
        this.limit = limit;
        filter = ff;
        dir = DirectoryStack.getNewStack(current);
    }

    public boolean hasNext() {
        while(walkOver());
        return isMore && (limit > count || limit < 0) && dir.getCurrent() != null;
    }

    private long count = 0;

    public File next() {
        File aux = dir.getCurrent();
        dir.advancePostition();
        count++;
        return aux;
    }

    private boolean walkOver() {
        if (dir.isOutOfDirListRange()) {
            if (dir.isCantGoParent()) {
                isMore = false;
                return false;
            } else {
                dir.goToParent();
                dir.advancePostition();
                return true;
            }
        } else {
            if (dir.isCurrentDirectory()) {
                if (dir.isDirectoryEmpty()) {
                    dir.advancePostition();
                } else {
                    dir.goIntoDir();
                }
                return true;
            } else {
                if (filter.accept(dir.getCurrent())) {
                    return false;
                } else {
                    dir.advancePostition();
                    return true;
                }
            }
        }
    }

    private boolean isMore = true;

    public void remove() {
        throw new UnsupportedOperationException();
    }

}

Beachten Sie, dass der Iterator Anschlag um einen Betrag von Dateien iterateds und es auch einen Filefilter hat.

Und DirectoryStack ist:

public class DirectoryStack {
    private class Element{
        private File files[] = null;
        private int currentPointer;
        public Element(File current) {
            currentPointer = 0;
            if (current.exists()) {
                if(current.isDirectory()){
                    files = current.listFiles();
                    Set<File> set = new TreeSet<File>();
                    for (int i = 0; i < files.length; i++) {
                        File file = files[i];
                        set.add(file);
                    }
                    set.toArray(files);
                }else{
                    throw new IllegalArgumentException("File current must be directory");
                }
            } else {
                throw new IllegalArgumentException("File current not exist");
            }

        }
        public String toString(){
            return "current="+getCurrent().toString();
        }
        public int getCurrentPointer() {
            return currentPointer;
        }
        public void setCurrentPointer(int currentPointer) {
            this.currentPointer = currentPointer;
        }
        public File[] getFiles() {
            return files;
        }
        public File getCurrent(){
            File ret = null;
            try{
                ret = getFiles()[getCurrentPointer()];
            }catch (Exception e){
            }
            return ret;
        }
        public boolean isDirectoryEmpty(){
            return !(getFiles().length>0);
        }
        public Element advancePointer(){
            setCurrentPointer(getCurrentPointer()+1);
            return this;
        }
    }
    private DirectoryStack(File first){
        getStack().push(new Element(first));
    }
    public static DirectoryStack getNewStack(File first){
        return new DirectoryStack(first);
    }
    public String toString(){
        String ret = "stack:\n";
        int i = 0;
        for (Element elem : stack) {
            ret += "nivel " + i++ + elem.toString()+"\n";
        }
        return ret;
    }
    private Stack<Element> stack=null;
    private Stack<Element> getStack(){
        if(stack==null){
            stack = new Stack<Element>();
        }
        return stack;
    }
    public File getCurrent(){
        return getStack().peek().getCurrent();
    }
    public boolean isDirectoryEmpty(){
        return getStack().peek().isDirectoryEmpty();
    }
    public DirectoryStack downLevel(){
        getStack().pop();
        return this;
    }
    public DirectoryStack goToParent(){
        return downLevel();
    }
    public DirectoryStack goIntoDir(){
        return upLevel();
    }
    public DirectoryStack upLevel(){
        if(isCurrentNotNull())
            getStack().push(new Element(getCurrent()));
        return this;
    }
    public DirectoryStack advancePostition(){
        getStack().peek().advancePointer();
        return this;
    }
    public File[] peekDirectory(){
        return getStack().peek().getFiles();
    }
    public boolean isLastFileOfDirectory(){
        return getStack().peek().getFiles().length <= getStack().peek().getCurrentPointer();
    }
    public boolean gotMoreLevels() {
        return getStack().size()>0;
    }
    public boolean gotMoreInCurrentLevel() {
        return getStack().peek().getFiles().length > getStack().peek().getCurrentPointer()+1;
    }
    public boolean isRoot() {
        return !(getStack().size()>1);
    }
    public boolean isCurrentNotNull() {
        if(!getStack().isEmpty()){
            int currentPointer = getStack().peek().getCurrentPointer();
            int maxFiles = getStack().peek().getFiles().length;
            return currentPointer < maxFiles;
        }else{
            return false;
        }
    }
    public boolean isCurrentDirectory() {
        return getStack().peek().getCurrent().isDirectory();
    }
    public boolean isLastFromDirList() {
        return getStack().peek().getCurrentPointer() == (getStack().peek().getFiles().length-1);
    }
    public boolean isCantGoParent() {
        return !(getStack().size()>1);
    }
    public boolean isOutOfDirListRange() {
        return getStack().peek().getFiles().length <= getStack().peek().getCurrentPointer();
    }

}
Beantwortet am 10/12/2008 um 13:18
quelle vom benutzer

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more