File.lastModified()速度极慢!

4
我正在进行文件的递归复制,并且像 xcopy /D 一样,我只想要复制更新的目标文件(由于需要在复制过程中更改一些文件,因此不能直接使用xcopy)。
在Java中,我使用 lastModified() 来检查目标文件是否比源文件旧,但速度非常慢。
  • 我能否加快这个过程(也许使用JNI?)?
  • 是否有其他可以更好地完成工作的复制脚本(复制新文件+正则表达式更改一些文本文件)?

无论如何都不会复制文件,因为这将比检查上次修改日期花费更多时间(通过网络复制)。

4个回答

4

您需要确定为什么速度如此缓慢。

当您运行程序时,您的进程的CPU利用率是多少。如果超过50%的用户,则应该能够优化您的程序;如果少于20%,则没有太多可以做的。

通常,这种方法很慢,因为您要检查的文件存储在磁盘而不是内存中。如果是这种情况,您需要加快访问磁盘的速度或获取更快的驱动器。例如,SSD可以比传统硬盘快10-100倍。

批量查询可能有所帮助。您可以使用多个线程来检查lastModified日期。例如,具有固定大小的线程池并为每个文件添加任务。线程池的大小确定一次轮询的文件数。

这样可以使操作系统重新排列请求以适合磁盘上的布局。注意:理论上这很好,但您必须测试它是否使您的操作系统/硬件更快,因为它可能会使事情变得更慢。;)


当这种方法运作良好时,操作系统可以重新排序请求以最小化磁头移动。常用的算法是电梯算法(http://en.wikipedia.org/wiki/Elevator_algorithm)。顺便说一下,Java程序无法知道访问文件的最佳顺序。 - Peter Lawrey

2
我在网络驱动器上发现了这个问题,非常痛苦。我有一个包含17000多个文件的目录。在本地驱动器上,检查最后修改日期不到2秒钟。但在网络驱动器上,需要58秒!我的应用程序是一个交互式应用程序,所以我收到了一些投诉。
经过一些研究,我决定可以实现一些JNI代码来执行Windows Kernel32 findfirstfile/findnextfile/findclose来显着改善这个过程,但随之而来的是32位和64位版本等等问题。并且失去了跨平台功能。
虽然有点恶心的黑客行为,但这就是我所做的。我的应用程序主要在Windows上运行,但我不想限制它只能在Windows上运行,所以我做了以下操作。检查是否正在操作Windows。如果是,则查看是否使用了本地硬盘。如果没有,则我们将使用hackish方法。
我存储了所有的大小写不敏感内容。对于其他可能同时拥有文件“ABC”和“abc”的目录的操作系统来说,这可能不是一个好主意。如果您需要关注这一点,则可以通过创建new File(“ABC”)和new File(“abc”)然后使用equals方法来比较它们。在大小写不敏感的文件系统(如Windows)中,它会返回true,但在Unix系统上会返回false。
尽管它可能有点黑客行为,但在网络驱动器上所需的时间从58秒降至1.6秒,所以我可以接受这个hack。
        boolean useJaveDefaultMethod = true;

    if(System.getProperty("os.name").startsWith("Windows"))
    {
        File f2 = f.getParentFile();
        while(true)
        {
            if(f2.getParentFile() == null)
            {
                String s = FileSystemView.getFileSystemView().getSystemTypeDescription(f2);
                if(FileSystemView.getFileSystemView().isDrive(f2) && "Local Disk".equalsIgnoreCase(s))
                {
                    useJaveDefaultMethod = true;
                }
                else
                {
                    useJaveDefaultMethod = false;
                }
                break;
            }
            f2 = f2.getParentFile();
        }
    }
    if(!useJaveDefaultMethod)
    {
        try
        {
            ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/C", "dir " + f.getParent());
            pb.redirectErrorStream(true);
            Process process = pb.start();
            InputStreamReader isr = new InputStreamReader(process.getInputStream());
            BufferedReader br = new BufferedReader(isr);

            String line;
            DateFormat df = new SimpleDateFormat("dd-MMM-yy hh:mm a");
            while((line = br.readLine()) != null)
            {
                try
                {
                    Date filedate = df.parse(line);
                    String filename = line.substring(38);
                    dirCache.put(filename.toLowerCase(), filedate.getTime());
                }
                catch(Exception ex)
                {

                }
            }
            process.waitFor();

            Long filetime = dirCache.get(f.getName().toLowerCase());
            if(filetime != null)
                return filetime;

        }
        catch(Exception Exception)
        {
        }
    }

    // this is SO SLOW on a networked drive!
    long lastModifiedDate = f.lastModified();
    dirCache.put(f.getName().toLowerCase(), lastModifiedDate);

    return lastModifiedDate;

那个救了我的应用程序!谢谢。实际上,如果你在Windows上,为什么不总是使用CLI呢?这会减少复杂性。 - Clerenz
我喜欢这种侧面思考方式,但是在非美国地区可能行不通,因为行格式可能会有很大的差异。 - paul

1

不幸的是,Java处理查找lastModified的方式很慢(基本上,它在您请求信息时查询每个文件的底层文件系统,没有对listFiles或类似数据的批量加载)。

您可以潜在地调用更有效的本机程序来批量执行此操作,但任何这样的解决方案都会与您部署到的平台密切相关。


1

我想你是在网络上进行这个操作,否则复制就没有什么意义了。网络目录操作很慢,真是倒霉。你可以尝试只复制小于某个大小阈值的文件,以减少总操作时间。

我不同意Kris的观点:Java的做法并没有什么明显的低效之处,而且无论如何它都必须这样做,因为你需要最新的值。


是的,你是对的,网络...但Peter Lawrey似乎也是对的! - dacwe

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