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

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

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

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

一个小基准

我刚做了一点基准。 运行

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

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

相关文章: 如何查看目录的子文件是否已更改

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

  1. 如果索引为空,则使用timestamp == dir.lastModified() – 1将根目录添加到索引。
  2. 查找索引中的所有目录
  3. 将索引中的目录的时间戳与文件系统中的目录的时间戳进行比较。 这是一个快速操作,因为您有完整的路径(没有扫描所涉及的树中的所有文件/目录)。
  4. 如果时间戳已更改,则此目录中有更改。 重新扫描并更新索引。
  5. 如果在此步骤中遇到缺少的目录,请从索引中删除子树
  6. 如果遇到现有目录,请忽略它(将在步骤2中检查)
  7. 如果遇到新目录,请使用timestamp == dir.lastModified() – 1添加它。 确保在步骤2中考虑它。

这将允许您以有效的方式注意新文件和已删除文件。 由于您在步骤#2中仅扫描已知路径,因此这将非常有效。 文件系统不能枚举目录中的所有条目,但是当您知道确切的名称时它们很快。

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

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

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

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

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

你能跳出java吗?

你可以简单地使用

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

/按名称排序

如果你把它输出到out.txt

然后执行diff到上次在Java或批处理文件中运行此文件。 在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。

不幸的是,没有标准的方法来监听java中的文件系统事件。 这可能是在java7中出现的。

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

一种可以加快速度的方法是迭代目录并检查上次修改时间以查看自上次索引以来目录的内容是否已更改,如果他们只是对目录执行了正常扫描,那么请参阅如果你能找到改变的地方。 我不知道这将是多么可移植,但它改变了层次结构在Linux系统上传播(可能与文件系统有关),所以你可以从根开始向下工作,当你点击一个目录时停止没改变

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

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

  1. File.listFiles()本质上很慢。 请参阅前面的问题“ Java是否存在解决大型目录的糟糕性能的方法? ”,此Java RFE“ File.list(FilenameFilter)对大型目录无效 ”以获取更多信息。 很快就会出现NIO.2这个缺点。

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

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

我听说这个任务很难有效地完成。 如果很容易的话,我肯定MS会在Windows上实现类似的工具,尤其是现在HD正在发展壮大。

这样的事情怎么样 :

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

(那里有很大的改进空间,因为那个版本一次读取一个字符:你可以从这里选择一个更好的版本来更快地读取流)

你用作参数:

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

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

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

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

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