代码之家  ›  专栏  ›  技术社区  ›  Pyrolistical

有没有解决Java在大型目录上的糟糕性能的方法?

  •  16
  • Pyrolistical  · 技术社区  · 16 年前

    我正在尝试一次处理一个通过网络存储的文件。读取文件很快,因为缓冲不是问题。我遇到的问题只是列出文件夹中的目录。在许多文件夹中,每个文件夹至少有10个文件。

    由于file.list()返回的是数组而不是iterable,所以性能非常慢。Java关闭并收集文件夹中的所有名称,并在返回之前将其打包成数组。

    这个的bug条目是 http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834 而且没有工作。他们只是说JDK7已经解决了这个问题。

    几个问题:

    1. 有人能解决这个性能瓶颈吗?
    2. 我是在努力实现不可能的事吗?即使只是在目录上迭代,性能仍然会很差吗?
    3. 我可以使用具有此功能的beta JDK7构建,而不必在其上构建整个项目吗?
    10 回复  |  直到 11 年前
        1
  •  7
  •   Bill K    11 年前

    虽然这并不漂亮,但我曾经通过在启动应用程序之前将dir/ls的输出管道传输到一个文件中,并传入文件名来解决了这类问题。

    如果你需要在应用程序中做这件事,你可以使用System.Exec(),但这会造成一些不愉快。

    你问。第一种形式会非常快,第二种形式也会非常快。

    请确保每行执行一项(裸、无装饰、无图形)、所选命令的完整路径和递归选项。

    编辑:

    30分钟就可以得到一个目录列表,哇。

    我突然想到,如果使用exec(),可以将它的stdout重定向到管道中,而不是将其写入文件。

    如果您这样做了,您应该立即开始获取文件,并且能够在命令完成之前开始处理。

    实际上,交互可能会减慢速度,但可能不会——您可以尝试一下。

    哇,我刚刚为您找到了.exec命令的语法,并遇到了这个问题,可能正是您想要的(它使用exec和“ls”列出了一个目录,并将结果通过管道传输到您的程序中进行处理): good link in wayback (j_¶rg在评论中提供,以取代 this one 神谕破灭的太阳)

    不管怎样,这个想法很简单,但是让代码正确是很烦人的。我去从互联网上窃取一些密码,然后把它们破解出来——brb

    /**
     * Note: Only use this as a last resort!  It's specific to windows and even
     * at that it's not a good solution, but it should be fast.
     * 
     * to use it, extend FileProcessor and call processFiles("...") with a list
     * of options if you want them like /s... I highly recommend /b
     * 
     * override processFile and it will be called once for each line of output.
     */
    import java.io.*;
    
    public abstract class FileProcessor
    {
       public void processFiles(String dirOptions)
       {
          Process theProcess = null;
          BufferedReader inStream = null;
    
          // call the Hello class
          try
          {
              theProcess = Runtime.getRuntime().exec("cmd /c dir " + dirOptions);
          }
          catch(IOException e)
          {
             System.err.println("Error on exec() method");
             e.printStackTrace();  
          }
    
          // read from the called program's standard output stream
          try
          {
             inStream = new BufferedReader(
                                    new InputStreamReader( theProcess.getInputStream() ));  
             processFile(inStream.readLine());
          }
          catch(IOException e)
          {
             System.err.println("Error on inStream.readLine()");
             e.printStackTrace();  
          }
    
       } // end method
       /** Override this method--it will be called once for each file */
       public abstract void processFile(String filename);
    
    
    } // end class
    

    感谢代码捐赠者 IBM

        2
  •  4
  •   OscarRyz    16 年前

    另一种方法是通过不同的协议来提供文件。正如我理解的那样,您正在使用SMB,Java只是试图将它们列为常规文件。

    这里的问题可能不只是Java(在使用微软Explorer x:\\共享)打开该目录时,它的行为在我的经验中也需要相当长的时间。

    您可以将协议更改为类似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文件信息提取到你的客户机上(我不知道这些信息有多少),而你只需要文件名来进行以后的处理。

    如果现在处理速度很快,可能会慢一点。这是因为预取的信息不再可用。

    试一试。

        3
  •  3
  •   kohlerm    16 年前

    我怀疑这个问题与你引用的错误报告有关。 问题在于“只”使用内存,但不一定要加快速度。 如果您有足够的内存,那么这个bug与您的问题不相关。

    你应该衡量你的问题是否与记忆有关。打开垃圾收集器日志,例如使用 gcviewer 分析您的内存使用情况。

    我怀疑这与导致问题的SMB协议有关。 您可以尝试用另一种语言编写一个测试,看看它是否更快,或者您可以尝试通过其他方法获取文件名列表,如在另一篇文章中所描述的。

        4
  •  3
  •   NiTiN    11 年前

    如何使用file.list(file name filter filter)方法和实现file name filter.accept(file dir,string name)来处理每个文件并返回false。

    我在Linux虚拟机上运行这个目录,其中包含10多个文件,用了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));
        }  
    }
    
        5
  •  2
  •   grepsedawk    16 年前

    一个不可移植的解决方案是对操作系统进行本机调用并传输结果。

    对于Linux

    你可以看到 readdir . 您可以像链接列表一样浏览目录结构,并批量或单独返回结果。

    对于Windows

    在Windows中,使用 FindFirstFile FindNextFile API。

        6
  •  1
  •   Yoni Roit    16 年前

    如果您最终需要处理所有文件,那么让iterable over string[]不会给您带来任何好处,因为您仍然需要去获取整个文件列表。

        7
  •  1
  •   Peter    12 年前

    如果你在Java 1.5或1.6上,清除“DIR”命令并在Windows上解析标准输出流是一种完全可接受的方法。我在过去使用这种方法处理网络驱动器,通常比等待本机java.io.file listfiles()方法返回要快得多。

    当然,JNI调用应该比炮击“dir”命令更快,更安全。以下JNI代码可用于使用Windows API检索文件/目录列表。这个函数可以很容易地重构成一个新的类,这样调用者就可以增量地检索文件路径(即一次获取一个路径)。例如,可以重构代码,以便在构造函数中调用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在引擎盖下做什么,但是如果您需要文件路径以外的其他文件属性(例如大小、mod日期等),我发现下面的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;
    }
    

    您可以在 javaxt-core 图书馆。在使用Java1.60E38的测试中,当Windows主机击中Windows共享时,我发现这个JNI方法大约快10倍,然后调用java. Io.ListListFes()或清除“DIR”命令。

        8
  •  0
  •   Bill K    16 年前

    我想知道为什么一个目录中有10万个文件。有些文件系统不能很好地处理这么多文件。对于文件系统有特定的限制,例如每个目录的最大文件量和子目录的最大级别。

    我用迭代器解决了一个类似的问题。

    我需要递归地遍历大型目录和多个级别的目录树。

    我尝试使用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();
        }
    
    }
    

    注意迭代器会被大量文件迭代器停止,它也有一个文件过滤器。

    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();
        }
    
    }
    
        9
  •  0
  •   Peter Lawrey    16 年前

    使用iterable并不意味着文件将被传输给您。事实上,它通常是相反的。所以数组通常比iterable快。

        10
  •  0
  •   MarkR    16 年前

    你确定这是因为Java,而不仅仅是一个目录中的10K条目,特别是网络上的一般问题吗?

    您是否尝试过使用win32 findfirst/findnext函数编写概念验证程序来在C中执行相同的操作,以查看它是否更快?

    我不知道SMB的输入和输出,但我强烈怀疑列表中的每个文件都需要一次往返行程——这不会很快,尤其是在具有中等延迟的网络上。

    在数组中有10k个字符串听起来像是不应该对现代Java VM征税太多的东西。

    推荐文章