如何快速查找添加/删除的文件?

时间:2009-01-26 13:19:13

标签: java file filesystems

我正在编写一个小程序,它创建了我目录中所有文件的索引。它基本上遍历磁盘上的每个文件并将其存储到可搜索的数据库中,就像Unix的locate一样。问题是,由于我有大约一百万个文件,所以索引生成非常慢。

生成索引后,是否可以快速找出自上次运行以来在磁盘上添加或删除的文件?

编辑:我不想监控文件系统事件。我认为风险太高而无法实现同步,我更喜欢快速重新扫描,以便快速找到添加/删除文件的位置。也许目录上次修改日期或其他什么?

一个小基准

我刚做了一点基准。运行

dir /b /s M:\tests\  >c:\out.txt

需要0.9秒,并提供我需要的所有信息。当我使用Java实现(much like this)时,大约需要4.5秒。任何想法如何改善至少这种蛮力方法?

相关帖子:How to see if a subfile of a directory has changed

10 个答案:

答案 0 :(得分:7)

你能跳出java吗?

你可以简单地使用

dir /b /s /on M:\tests\  

/按名称排序

如果你把它传递给out.txt

然后在上次以Java或批处理文件运行此文件时执行diff操作。在Dos中有类似的东西。你需要得到一个diff工具,不管是cygwin中的diff还是优秀的http://gnuwin32.sourceforge.net/packages/diffutils.htm

dir /b /s /on m:\tests >new.txt
diff new.txt archive.txt >diffoutput.txt
del archive.txt
ren new.txt archive.txt

显然你也可以使用java diff类,但我认为接受的是shell命令几乎总是在文件列表操作中击败Java。

答案 1 :(得分:6)

不幸的是,没有标准的方法来监听java中的文件系统事件。这个可以进入java7。

目前,您必须google“java filesystem events”并选择与您的平台相匹配的自定义实现。

答案 2 :(得分:4)

我在我的工具MetaMake中完成了这项工作。这是食谱:

  1. 如果索引为空,请将根目录添加到索引中,时间戳为== dir.lastModified() - 1。
  2. 查找索引中的所有目录
  3. 将索引中目录的时间戳与文件系统中的目录进行比较。这是一个快速操作,因为你有完整的路径(没有扫描所涉及的树中的所有文件/目录)。
  4. 如果时间戳已更改,则此目录中有更改。重新扫描并更新索引。
  5. 如果在此步骤中遇到丢失的目录,请从索引中删除子树
  6. 如果遇到现有目录,请忽略它(将在步骤2中检查)
  7. 如果遇到新目录,请使用timestamp == dir.lastModified() - 1添加目录。确保在步骤2中考虑它。
  8. 这将允许您以有效的方式注意新文件和已删除文件。由于您在步骤#2中仅扫描已知路径,因此这将非常有效。文件系统很难枚举目录中的所有条目,但是当你知道确切的名称时它们很快。

    缺点:您不会注意到已更改的文件。因此,如果您编辑文件,则会反映更改目录。如果您还需要此信息,则必须对索引中的文件节点重复上述算法。这次,您可以忽略新/已删除的文件,因为它们在运行目录期间已经更新。

    [编辑]扎克提到时间戳不够。我的回答是:没有其他方法可以做到这一点。对于目录和从实现到实现的更改,“大小”的概念是完全未定义的。没有API可以注册“我希望收到有关文件系统中某些内容的任何更改的通知”。有些API可以在您的应用程序处于活动状态时工作,但如果它停止或错过了某个事件,那么您就会失去同步。

    如果文件系统是远程的,事情会变得更糟,因为各种网络问题都可能导致您失去同步。因此,虽然我的解决方案可能不是100%完美和防水,但它将适用于除了最结构的特殊情况之外的所有情况。而且这是迄今为止唯一的解决方案。

    现在有一种类型的应用程序需要在修改后保留目录的时间戳:病毒或蠕虫。这显然会破坏我的算法,但是,它并不意味着防止病毒感染。如果你想要防范这种情况,你必须采取完全不同的方法。

    实现Zach想要的唯一其他方法是构建一个新的文件系统,将该信息永久记录到某个地方,将其出售给Microsoft并等待几年(可能是10个或更多)直到每个人都使用它。

答案 3 :(得分:3)

一种可以加快速度的方法是迭代目录并检查上次修改时间,看看自上次索引以来目录内容是否发生了变化,以及是否只是对目录进行了正常扫描然后看看你是否能找到改变的地方。我不知道这将是多么可移植,但它改变了层次结构在Linux系统上传播(可能是依赖于文件系统的),所以你可以从根开始向下工作,当你点击一个目录时停止没改变

答案 4 :(得分:1)

鉴于我们不想监视文件系统事件,我们是否可以跟踪每个文件的(name,size,time,checksum)?文件校验和(或加密哈希,如果您愿意)的计算将成为瓶颈。您可以在初始运行中计算一次,并且仅在必要时重新计算它(例如,当文件与其他三个属性匹配时)。当然,如果我们只想跟踪文件名而不是文件内容,我们就不需要为此烦恼。

您提到您的Java实现(类似于this)与“dir /s”相比非常慢。我认为有两个原因:

  1. File.listFiles()本来就很慢。有关详细信息,请参阅前面的问题“Is there a workaround for Java’s poor performance on walking huge directories?”和此Java RFE“File.list(FilenameFilter) is not effective for huge directories”。 NIO.2很快就会解决这个缺点。

  2. 您是否使用递归遍历目录?如果是这样,请尝试非递归方法,例如推送/弹出目录以在堆栈上/从堆栈访问。我的limited personal experience表明改进可能非常重要。

答案 5 :(得分:0)

文件日期方法可能不是最佳方法。例如,如果从备份还原文件。也许在索引期间,您可以存储文件内容的MD5哈希值。但是,您可能需要进行一些性能基准测试,以确定性能是否可接受

答案 6 :(得分:0)

我听说这个任务很难高效完成。如果很容易的话,我确信MS会在Windows上实现类似的工具,特别是现在HD正在发展壮大。

答案 7 :(得分:0)

我没有检查实现或性能,但是commons-io有一个listFiles()方法。值得一试。

答案 8 :(得分:0)

like this

private static String execute( String command ) throws IOException  { 
    Process p = Runtime.getRuntime().exec( "cmd /c " + command );
    InputStream i = p.getInputStream();
    StringBuilder sb = new StringBuilder();
    for(  int c = 0 ; ( c =  i.read() ) > -1  ; ) {
        sb.append( ( char ) c );
    }
    i.close();
    return sb.toString();
}

(那里有一个很多的改进空间,因为那个版本一次读取一个字符: 您可以选择更好的版本from here来更快地阅读流

你用作参数:

"dir /b /s M:\tests\"

如果要在正在运行的应用程序中使用它(而不是独立应用程序),您可以打折JVM的“预热”时间,大约1-2秒,具体取决于您的硬件。

您可以尝试一下,看看有什么影响。

答案 9 :(得分:0)

尝试使用git。版本控制软件面向这类问题,而git在速度方面享有良好的声誉;它专为快速使用本地文件而设计。 'git diff --name-status'会让你想到我想要的。