23

ネットワーク経由で保存されているファイルを一度に 1 つずつ処理しようとしています。バッファリングが問題ではないため、ファイルの読み取りは高速です。私が抱えている問題は、フォルダー内のディレクトリをリストすることだけです。多くのフォルダーで、フォルダーごとに少なくとも 10,000 個のファイルがあります。

File.list() は iterable ではなく配列を返すため、パフォーマンスが非常に遅くなります。Java はオフになり、フォルダー内のすべての名前を収集し、それを配列にパックしてから返します。

このバグ エントリはhttp://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834であり、回避策はありません。彼らは、これがJDK7で修正されたと言っています。

いくつかの質問:

  1. このパフォーマンスのボトルネックに対する回避策はありますか?
  2. 私は不可能を達成しようとしていますか?ディレクトリを反復するだけでもパフォーマンスは低下しますか?
  3. プロジェクト全体をビルドしなくても、この機能を備えたベータ JDK7 ビルドを使用できますか?
4

10 に答える 10

9

きれいではありませんが、アプリを起動する前に dir/ls の出力をファイルにパイプし、ファイル名を渡すことで、この種の問題を一度解決しました。

アプリ内でそれを行う必要がある場合は、system.exec() を使用することもできますが、これを行うと厄介な問題が発生します。

あなたは尋ねました。最初のフォームは非常に高速になり、2 番目のフォームもかなり高速になるはずです。

選択したコマンドのフル パスと再帰オプションを 1 行に 1 項目 (むき出し、装飾なし、グラフィックなし) 実行するようにしてください。

編集:

ディレクトリのリストを取得するだけで 30 分です。

exec() を使用すると、stdout をファイルに書き込む代わりにパイプにリダイレクトできることに気付きました。

これを行った場合は、すぐにファイルの取得を開始し、コマンドが完了する前に処理を開始できるはずです。

インタラクションにより、実際には速度が低下する場合がありますが、そうではない場合もあります。試してみてください。

うわー、私はあなたのために .exec コマンドの構文を見つけに行ったところ、これに出くわしました。おそらくまさにあなたが望むものです(execと「ls」を使用してディレクトリをリストし、処理のために結果をプログラムにパイプします): 良いリンクin wayback (オラクルが壊れた太陽からこれを置き換えるためにJörgがコメントで提供しました)

とにかく、アイデアは簡単ですが、コードを正しくするのは面倒です。インターネットからいくつかのコードを盗み出し、それらをハッキングします--brb

/**
 * 注: これは最後の手段としてのみ使用してください。それはウィンドウに固有のものであり、
 * 良い解決策ではありませんが、高速である必要があります。
 *
 * 使用するには、FileProcessor を拡張し、リストで processFiles("...") を呼び出します
 * /s のようなオプションが必要な場合は、/b を強くお勧めします
 *
 * processFile をオーバーライドすると、出力の各行に対して 1 回呼び出されます。
 */
java.io.* をインポートします。

パブリック抽象クラス FileProcessor
{
   public void processFiles(String dirOptions)
   {
      プロセス theProcess = null;
      BufferedReader inStream = null;

      // Hello クラスを呼び出す
      試す
      {
          theProcess = Runtime.getRuntime().exec("cmd /c dir " + dirOptions);
      }
      キャッチ (IOException e)
      {
         System.err.println("exec() メソッドのエラー");
         e.printStackTrace();  
      }

      // 呼び出されたプログラムの標準出力ストリームから読み取る
      試す
      {
         inStream = 新しい BufferedReader(
                                new InputStreamReader( theProcess.getInputStream() ));  
         processFile(inStream.readLine());
      }
      キャッチ (IOException e)
      {
         System.err.println("inStream.readLine() のエラー");
         e.printStackTrace();  
      }

   } // メソッド終了
   /** このメソッドをオーバーライドします。ファイルごとに 1 回呼び出されます */
   public abstract void processFile(String filename);


} // クラス終了

そして、 IBMのコード提供者に感謝します

于 2008-12-10T00:57:17.247 に答える
8

File.list(FilenameFilter filter) メソッドを使用し、FilenameFilter.accept(File dir, String name) を実装して、各ファイルを処理し、false を返すようにするのはどうですか。

これを Linux vm で 10,000 個以上のファイルを含むディレクトリに対して実行したところ、10 秒未満かかりました。

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));
    }  
}
于 2013-10-22T14:20:45.590 に答える
4

別の方法は、ファイルを別のプロトコルで提供することです。私が理解しているように、あなたはそのためにSMBを使用しており、Javaはそれらを通常のファイルとしてリストしようとしているだけです。

ここでの問題はJavaだけではないかもしれません(Microsoft Explorer x:\ sharedでそのディレクトリを開いたときの動作)私の経験では、かなりの時間がかかります。

プロトコルをHTTPのようなものに変更して、ファイル名をフェッチすることだけができます。このようにして、http経由でファイルのリストを取得し(10k行は多すぎないようにする必要があります)、サーバーにファイルリストを処理させることができます。ローカルリソース(サーバー内のリソース)で実行されるため、これは非常に高速です。

次に、リストができたら、現在行っている方法とまったく同じ方法でリストを処理できます。

重要な点は、ノードの反対側に補助メカニズムを配置することです。

これは実行可能ですか?

今日:

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

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

提案:

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

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

httpサーバーは、非常に小さくて単純なファイルである可能性があります。

これが現在の方法である場合、後で処理するためにファイル名のみが必要なときに、すべての10kファイル情報をクライアントマシンにフェッチすることです(その情報の量はわかりません)。 。

現在処理が非常に速い場合は、少し遅くなる可能性があります。これは、プリフェッチされた情報が利用できなくなったためです。

試してみる。

于 2008-12-10T00:36:42.407 に答える
3

移植性のない解決策は、オペレーティングシステムにネイティブ呼び出しを行い、結果をストリーミングすることです。

Linuxの場合

readdirのようなものを見ることができます。リンクリストのようにディレクトリ構造をたどり、結果をバッチまたは個別に返すことができます。

Windowsの場合

Windowsでは、 FindFirstFileAPIFindNextFileAPIを使用した場合の動作はかなり似ています。

于 2008-12-10T00:22:43.370 に答える
3

問題が参照したバグレポートに関連しているとは思えません。問題は「のみ」のメモリ使用量ですが、必ずしも速度ではありません。十分なメモリがある場合、バグは問題には関係ありません。

問題がメモリに関連しているかどうかを測定する必要があります。ガベージ コレクター ログをオンにし、たとえばgcviewerを使用してメモリ使用量を分析します。

問題の原因となっている SMB プロトコルに関係していると思われます。別の言語でテストを作成して高速かどうかを確認するか、別の投稿で説明されているような別の方法でファイル名のリストを取得してみてください。

于 2008-12-10T08:49:35.593 に答える
1

最終的にすべてのファイルを処理する必要がある場合は、ファイルのリスト全体を取得する必要があるため、String[]を介してIterableを使用しても利点はありません。

于 2008-12-10T12:26:09.167 に答える
1

Java 1.5 または 1.6 を使用している場合、Windows で「dir」コマンドをシェル化し、標準出力ストリームを解析することは、完全に受け入れられるアプローチです。私は過去にネットワークドライブを処理するためにこのアプローチを使用しましたが、通常、ネイティブの java.io.File listFiles() メソッドが戻るのを待つよりもはるかに高速でした。

もちろん、JNI 呼び出しは、"dir" コマンドを実行するよりも高速で、潜在的に安全です。次の JNI コードを使用すると、Windows API を使用してファイル/ディレクトリのリストを取得できます。この関数は新しいクラスに簡単にリファクタリングできるため、呼び出し元はファイル パスを段階的に取得できます (つまり、一度に 1 つのパスを取得できます)。たとえば、FindFirstFileW がコンストラクターで呼び出されるようにコードをリファクタリングし、FindNextFileW を呼び出す別のメソッドを持つことができます。

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;
}

クレジット: https://sites.google.com/site/jozsefbekes/Home/windows-programming/miscellaneous-functions

このアプローチを使用しても、まだ効率が向上する可能性があります。java.io.File へのパスをシリアル化すると、特にパスがネットワーク ドライブ上のファイルを表す場合に、パフォーマンスが大幅に低下します。Sun/Oracle が内部で何をしているのかはわかりませんが、ファイル パス以外の追加のファイル属性 (サイズ、変更日など) が必要な場合は、次の JNI 関数の方が Java をインスタンス化するよりもはるかに高速であることがわかりました。ネットワーク上の .io.File オブジェクト パス。

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;
}

この JNI ベースのアプローチの完全な動作例は、javaxt-coreライブラリにあります。Windows共有にアクセスするWindowsホストでJava 1.6.0_38を使用した私のテストでは、このJNIアプローチがjava.io.File listFiles()の呼び出しまたは「dir」コマンドのシェル化よりも約10倍高速であることがわかりました。

于 2013-01-27T17:30:10.217 に答える
0

ディレクトリに10kのファイルがあるのはなぜだろうか。一部のファイル システムは、非常に多くのファイルでうまく機能しません。ディレクトリごとのファイルの最大量やサブディレクトリのレベルの最大量など、ファイル システムには特定の制限があります。

同様の問題を反復子ソリューションで解決します。

巨大なディレクトリといくつかのレベルのディレクトリ ツリーを再帰的に移動する必要がありました。

Apache commons io の FileUtils.iterateFiles() を試してみます。ただし、リストにすべてのファイルを追加してから List.iterator() を返すことにより、反復子を実装します。記憶力が非常に悪いです。

したがって、私は次のように書くことを好みます。

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();
    }

}

イテレータは反復されたファイルの量で停止し、FileFilter も持っていることに注意してください。

そして DirectoryStack は次のとおりです。

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();
    }

}
于 2008-12-10T12:18:16.267 に答える
0

Iterable を使用しても、ファイルがストリーミングされるわけではありません。実際、通常は逆です。したがって、配列は通常、Iterable よりも高速です。

于 2009-03-01T07:56:31.117 に答える