如何快速找到添加/删除的文件?
Posted
技术标签:
【中文标题】如何快速找到添加/删除的文件?【英文标题】:How to quickly find added / removed files? 【发布时间】:2009-01-26 13:19:13 【问题描述】:我正在编写一个小程序,它为我的目录中的所有文件创建索引。它基本上遍历磁盘上的每个文件并将其存储到可搜索的数据库中,就像 Unix 的定位一样。问题是,索引生成速度很慢,因为我有大约一百万个文件。
一旦我生成了索引,是否有一种快速的方法可以找出自上次运行以来磁盘上添加或删除了哪些文件?
编辑:我不想监控文件系统事件。我认为风险太高而无法同步,我更喜欢快速重新扫描之类的东西,它可以快速找到添加/删除文件的位置。也许与目录上次修改日期或其他什么?
一个小基准
我只是做了一个小基准测试。跑步
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
【问题讨论】:
我们是否可以假设我们只对添加和删除的文件感兴趣,即我们只索引文件名,其他所有内容(例如大小、上次修改时间、加密哈希)都无关紧要? 我们可以假设它仅适用于 Windows 平台吗?您可以使用 Microsoft 提供的索引服务(而不是自己滚动)吗? 另外,为什么您认为监视文件系统事件会存在不同步的风险?我不知道在 Java 中,但在 C#/.NET 中,您可以创建事件侦听器,在添加或删除给定路径下的文件或目录时触发,并且它 100% 的时间都可以工作...... @Coder,因为这样就要求应用程序一直在运行。也许最好像 Aaron Digulla 的回答那样快速扫描目录更改,然后在应用程序运行时使用事件侦听器。 【参考方案1】:你能跳出java吗?
你可以简单地使用
dir /b /s /on M:\tests\
/on 按名称排序
如果你把它输出到 out.txt
然后对您上次在 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。
【讨论】:
【参考方案2】:不幸的是,在 java 中没有标准的监听文件系统事件的方法。这可能来自 java7。
现在,您必须搜索“java 文件系统事件”并选择与您的平台匹配的自定义实现。
【讨论】:
【参考方案3】:我已经在我的工具 MetaMake 中完成了这项工作。这是食谱:
-
如果索引为空,则将根目录添加到索引中,时间戳 == dir.lastModified()-1。
查找索引中的所有目录
将索引中目录的时间戳与文件系统中的时间戳进行比较。这是一个快速操作,因为您拥有完整路径(无需扫描相关树中的所有文件/目录)。
如果时间戳已更改,则说明此目录发生了更改。重新扫描并更新索引。
如果在此步骤中遇到缺少目录,请从索引中删除子树
如果遇到现有目录,请忽略它(将在第 2 步中检查)
如果遇到新目录,请使用 timestamp == dir.lastModified()-1 添加它。确保它在第 2 步中得到考虑。
这将使您能够有效地注意到新文件和已删除文件。由于您在步骤 #2 中仅扫描已知路径,因此这将非常有效。文件系统不擅长枚举目录中的所有条目,但是当您知道确切名称时它们会很快。
缺点:您不会注意到更改的文件。因此,如果您编辑文件,这将不会反映在目录的更改中。如果您也需要此信息,则必须对索引中的文件节点重复上述算法。这一次,您可以忽略新/删除的文件,因为它们在目录运行期间已经更新。
[EDIT] Zach 提到时间戳是不够的。我的回答是:根本没有其他方法可以做到这一点。对于目录和从实施到实施的变化,“大小”的概念是完全未定义的。没有 API 可以在其中注册“我希望在对文件系统中的某些内容进行任何更改时收到通知”。有些 API 可以在您的应用程序处于活动状态时工作,但如果它停止或错过某个事件,那么您就不同步了。
如果文件系统是远程的,情况会变得更糟,因为各种网络问题都会导致您无法同步。因此,虽然我的解决方案可能不是 100% 完美且防水,但它适用于除构造最完善的特殊情况之外的所有情况。而且它是唯一能做到这一点的解决方案。
现在有一种应用程序希望在进行修改后保留目录的时间戳:病毒或蠕虫。这显然会破坏我的算法,但它并不是为了防止病毒感染。如果您想防止这种情况发生,您必须采用完全不同的方法。
实现 Zach 想要的唯一其他方法是构建一个新的文件系统,将这些信息永久记录在某个地方,然后将其出售给 Microsoft,然后等待几年(可能 10 年或更长时间),直到每个人都使用它。
【讨论】:
到目前为止,这是最好的答案。您知道这是文件系统特有的功能,还是可以在任何地方使用它? 它适用于 Unix 文件系统和 Windows。我没有用 HFS 测试过,但如果有任何问题,我会感到惊讶。 @Aaron:这种方法是否假定目录最后修改的时间戳在添加/删除文件时会发生变化?如果在外部修改目录时间戳会发生什么,例如通过“触摸”? (续) (cont'd) 此外,时间戳的使用在某些文件系统上可能不够好,例如FAT 只有 2 秒的精度,因此快速发生的变化可能不会被发现。 @Zach:然后你就白读了目录。但是几乎没有损坏,因为您只会读取该单个目录。【参考方案4】:您可以加快速度的一种方法是遍历目录并检查上次修改时间以查看自上次索引以来目录的内容是否发生了变化,以及他们是否刚刚对目录进行了正常扫描然后看看你是否能找到变化的地方。我不知道这将是多么可移植,但是它改变层次结构会在 Linux 系统上传播(可能取决于文件系统),因此您可以从根目录开始并向下工作,当您点击一个目录时停止'没有改变
【讨论】:
您必须检查每个目录,因为 Windows 不会传播,但这仍然会更快,因为您知道上次索引运行的所有路径。我以前做过这个,它有效。加速是巨大的!【参考方案5】:鉴于我们不想监视文件系统事件,我们是否可以只跟踪每个文件的(name,size,time,checksum)
?文件校验和(或加密哈希,如果您愿意)的计算将成为瓶颈。您可以在初始运行时只计算一次,然后仅在必要时重新计算(例如,当文件在其他三个属性上匹配时)。当然,如果我们只想跟踪文件名而不是文件内容,则无需为此烦恼。
您提到与“dir /s
”相比,您的 Java 实现(类似于 this)非常慢。我认为有两个原因:
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 显然解决了这个缺点,即将推出。
您是否正在使用递归遍历您的目录?如果是这样,请尝试使用非递归方法,例如在堆栈上/从堆栈上推送/弹出要访问的目录。我的limited personal experience 表明改进可能非常显着。
【讨论】:
【参考方案6】:文件日期方法可能不是最好的。例如,如果您从备份中恢复文件。也许在索引期间,您可以存储文件内容的 MD5 哈希。但是,您可能需要进行一些性能基准测试以查看性能是否可以接受
【讨论】:
我写了一些类似的东西来查找我硬盘上的重复文件。 MD5 很慢。我尝试对程序进行多线程处理,但它只是让我的计算机陷入了爬行。 计算 MD5 哈希需要读取整个文件。如果您正在寻找的是性能,那并不是很好...... 确实如此。这就是为什么我建议进行基准测试以检查性能是否可以接受。任何想法如何处理从备份中恢复的文件?【参考方案7】:我听说这项任务很难有效地完成。我敢肯定,如果它很容易,MS 会实现与 Windows 类似的工具,尤其是在 HD:s 越来越多的今天。
【讨论】:
【参考方案8】:来点什么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 秒。
你可以试试看有什么影响。
【讨论】:
我已经尝试过这种方式,但是 dir 的愚蠢排序使其在扫描层次结构时难以使用。 @martinus: mmhh 在以下几行中: String s = getFileList(); Arrays.sort(s.split(lineSep));也许?【参考方案9】:尝试使用 git。版本控制软件就是针对这类问题的,git以速度着称;它专为快速处理本地文件而设计。 'git diff --name-status' 会让你得到你想要的。
【讨论】:
我想索引包含数百万个文件的大型文件系统【参考方案10】:我没有检查实现或性能,但是 commons-io 有一个listFiles() 方法。可能值得一试。
【讨论】:
它只是调用 Java 的列表文件以上是关于如何快速找到添加/删除的文件?的主要内容,如果未能解决你的问题,请参考以下文章