加快文件系统访问速度?

10

我的应用程序扫描文件系统的一部分,用户报告扫描网络驱动器时非常慢。通过测试我的代码,我确定了瓶颈:方法File.isFile()File.isDirectory()File.isHidden()都在调用fs.getBooleanAttributes(File f)。这个方法在Windows网络驱动器上似乎非常缓慢。如何提高性能?我是否可以以某种方式避免调用此方法?

5个回答

10

防御性编程经常会调用那些isXYZ()方法,这通常是一个好的实践。但是,有时候性能会很差,就像你发现的那样。

另一种方法是假设文件是存在的、可见的、可读的等等,并尝试读取它。如果不符合条件,就会抛出异常,您可以捕获异常,然后进行检查以找出出了什么问题。这样,您就可以针对通用情况进行优化(即一切正常),只有在发生错误时才执行较慢的操作。


哦,这是一个非常有趣的看待问题的方式,但不幸的是在我的情况下并不适用。我正在构建一个反映用户文件结构的树形结构,所以我必须弄清楚一个文件是文件还是目录。虽然,我想我可以使用listFiles()来区分它们...谢谢,你给了我一些思考的东西! - Amanda S
+1 我喜欢优化“常见情况”的观点。您仍然可以保持防御性检查以处理某些不是文件的情况。 - mpeterson
+1 我刚刚修改了一些递归目录树处理代码,使用listFiles来确定“文件”是否为目录;这对于本地和局域网连接的驱动器是一个小的加速 - 直到我回家才能测试VPN远程驱动器。它也是更紧凑的代码。 - Lawrence Dol
2
如果你必须调用这些方法,请按照允许你在大多数情况下进行短路的顺序进行。如果isFile()为真,则没有理由调用isDirectory(),如果isHidden()为真,则也没有理由调用任何一个。 - Chris Nava

6
你是如何构建这个文件列表的?除非你要同时显示系统上的每个文件,否则你应该有一些选项...
  1. 只有在用户请求时处理这些信息。例如,他们点击“Windows”文件夹时,你可以处理Windows内的文件。
  2. 在后台线程中处理这些信息,给人以更快的响应时间的错觉。
也许如果你展示一下用于构建列表的代码,我们可以找到其他改进的地方。(为什么不能根据获取信息的方法推断类型?如果你调用像GetFiles()这样的方法,难道你不已经知道返回的所有东西都是文件吗?)

你说得对,我可以懒惰一点,只有在用户需要时才加载文件夹,但这样每次用户尝试打开一个新文件夹时都会有一个暂停,可能会让整个树形结构感觉迟缓。这是在开始时花费很长时间和一直变慢之间的权衡...但也许情况并不会那么糟糕。我需要进行测试。谢谢你的建议! - Amanda S
1
这只是在网络上,对吧?本地的事情足够快了吗?不确定是否重要,但您使用哪种协议进行连接? - mpeterson
将“+1”放入后台处理,以提高用户的感知性能。 - rob
是的,本地运行相当快。不确定我们使用什么协议连接到网络驱动器... - Amanda S

3
我遇到了完全相同的问题。
我们的解决方案很简单:由于我们的目录结构遵循标准(没有目录名中包含“.”字符的情况),我只需遵循标准,并应用一个非常简单的启发式方法:“在我们的情况下,目录名称中不包含'.'字符”。这个简单的启发式方法大大减少了我们的应用程序调用java.io.File类的isDirectory()函数的次数。
也许这也是你的情况。也许在你的目录结构中,你可以通过文件命名约定知道一个文件是否是目录。

2
个人而言,我非常不愿意编写依赖于“事物应该如何”的代码。并不是说这从来不是一个好的解决方案,但它是危险的。如果有人不知道标准或由于任何原因不遵循标准,你的代码将给出错误的结果。在数据错误时给出错误消息是一回事,但完全失败是另一回事。 - Jay
如果我有那种可以依赖的标准就好了,但不幸的是我没有。:o) - Amanda S

2
这里是使用listFilesisDirectory遍历目录树的代码示例,下面是优化前后的对比结果(我的代码使用了一个通用回调函数来处理每个文件夹和文件,如果我在编写C#代码,这将是一个委托)。
可以看出,listFiles方法实际上更加简洁易懂,而且在本地硬盘(950毫秒 vs 1000毫秒)和局域网驱动器(26秒 vs 28秒)上都稍微快一些,这两种情况下处理的文件数为23,000个。
虽然对于远程连接驱动器,加速可能会显著,但我无法在工作时测试。有点令人惊讶的是,通过Windows RAS VPN连接到网络驱动器,加速仍然只有约10%。 新代码:
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    dir=dir.getAbsoluteFile();
    return _processDirectory(dir.getParentFile(),dir,new Callback.WithParams(cbk,2),sel);
    }

static private int _processDirectory(File par, File fil, Callback.WithParams cbk, FileSelector sel) {
    File[]                              ents=(sel==null ? fil.listFiles() : fil.listFiles(sel));    // listFiles returns null if fil is not a directory
    int                                 cnt=1;

    if(ents!=null) {
        cbk.invoke(fil,null);
        for(int xa=0; xa<ents.length; xa++) { cnt+=_processDirectory(fil,ents[xa],cbk,sel); }
        }
    else {
        cbk.invoke(par,fil);                                                    // par can never be null
        }
    return cnt;
    }

旧代码

static public int oldProcessDirectory(File dir, Callback cbk, FileSelector sel) {
    dir=dir.getAbsoluteFile();
    return _processDirectory(dir,new Callback.WithParams(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParams cbk, FileSelector sel) {
    File[]                              ents=(sel==null ? dir.listFiles() : dir.listFiles(sel));
    int                                 cnt=1;

    cbk.invoke(dir,null);

    if(ents!=null) {
        for(int xa=0; xa<ents.length; xa++) {
            File                        ent=ents[xa];

            if(!ent.isDirectory()) {
                cbk.invoke(dir,ent);
                ents[xa]=null;
                cnt++;
                }
            }
        for(int xa=0; xa<ents.length; xa++) {
            File                        ent=ents[xa];

            if(ent!=null) {
                cnt+=_processDirectory(ent,cbk,sel);
                }
            }
        }
    return cnt;
    }

0

如果你还没有尝试过,建议自己调用getBooleanAttributes并执行必要的掩码操作,这样如果你对同一个文件进行多次检查就会更快。虽然这不是一个完美的解决方案(也会让你的代码变得与平台有关),但它可以将性能提高3到4倍。即使速度没有达到应有的水平,但仍然是相当显著的性能提升。

JDK7的java.nio.file.Path功能应该对此有所帮助。

最后,如果你在终端用户环境中有任何控制权,请建议用户配置其防病毒软件以不扫描网络驱动器。许多大型杀毒软件(不确定它们要解决什么问题)默认开启此功能。我不知道这会对各种File方法产生什么影响,但我们发现配置不正确的防病毒软件几乎会导致网络资源上的所有文件访问出现延迟问题。


如果我能自己调用getBooleanAttributes(),那确实会更快,但不幸的是,我受到Java 1.5的限制,而java.io.FileSystem是包保护的!愚蠢的抽象屏障总是妨碍我。 :o) - Amanda S
你应该能够使用反射获取方法(请参阅Method.setAccessible())。另一个可能有效的技巧(我不确定jvm是否会拒绝未经Sun签名的java.io包中的类)是创建自己的类,该类位于同一文件夹中(只需将其放在jar文件中的java.io文件夹中)。 - Kevin Day

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接