如何确定文件是否已被修改?

21

我正在编写一款备份方案。它的简单实现方式是将文件从C:\位置复制到Z:\位置。

为了保证速度快,复制和粘贴之前会检查原始文件是否存在。如果存在,程序会进行一些“计算”来确定是继续复制还是备份文件已经是最新的。这些计算是我觉得很困难的。

起初,我比较了文件大小,但这并不够好,因为很有可能更改文件后它的大小是一样的(例如,在记事本中保存字符“C”与保存字符“T”的大小相同)。

所以,我需要找出修改日期是否有变动。目前,我使用 FileInfo 类获取文件信息,但在审查了所有字段后,似乎没有一个适合的参数。

我该如何检查我要复制的文件是否被修改过?

编辑:我在Stack Overflow上看到了使用MD5校验和的建议,但我担心一些要比较的文件达到了10GB。


2
大多数文件系统都有一个很好的元属性,通常称为“最后修改时间”。 - user703016
1
FileInfo.LastWriteTime没有这个信息吗?这是我从这个问题中得到的印象:https://dev59.com/M3M_5IYBdhLWcg3w4nfb - JoshVarty
1
也许这会有帮助:https://dev59.com/p3M_5IYBdhLWcg3wfTI0#1358529 - Bridge
1
@DaveRook 那个问题中的其他答案也值得一看。 :-) - Bridge
1
除了逐字节比较这两个文件,这很可能会降低速度,没有其他方法可以检查文件中的任何一个字节是否可能被更改。 - Mike Marynowski
显示剩余5条评论
6个回答

28

按修改日期进行备份可能不太可靠 - 计算机时钟在同步或手动调整时可能会倒退。 有些程序在管理修改日期方面修改或复制文件时可能无法很好地使用。

按归档位备份可能在受控环境下有效,但如果运行使用归档位的其他软件会发生什么情况呢?

Windows 归档位是邪恶的,必须停止使用

如果您想要(几乎)完全可靠,则应使用像 SHA1 这样的良好哈希函数存储上次备份版本的哈希值,如果哈希值更改,则上传新副本。

这里是 SHA1 类以及底部的代码示例:

http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha1.aspx

只需通过它运行文件字节并存储哈希值。 传递一个 FileStream 而不是使用字节数组将文件加载到内存中,以减少内存使用,特别是对于大文件。

您可以以各种方式将其与修改日期结合使用以根据需要调整程序的速度和可靠性。 例如,可以检查大多数备份的修改日期,并定期运行哈希检查器,以确保没有遗漏。 有时,修改日期会更改,但文件内容仍然相同(即使用相同数据进行了重写),在这种情况下,您可以在重新计算哈希并意识到它仍然相同时避免重新发送整个文件。

大多数版本控制系统都使用某种组合方法来处理哈希和修改日期。

如果不想每次进行完全备份并发送所有数据,则您的方法通常涉及某种风险管理与性能和可靠性之间的平衡。出于这个原因,偶尔进行“完全备份”非常重要。


为了明确,当你说“存储哈希”时,你是指在外部文件或数据库(或类似物)中吗? - Dave
3
这取决于你的系统如何实现 :) 你可以保留一个数值数据库,或者像Subversion以前所做的那样,在备份位置内创建一个隐藏目录,其中包含所有已备份文件的哈希值。Subversion已经不再这样做了,现在只在版本控制目录结构的根目录中保留一个隐藏目录中的数据库。 - Mike Marynowski
我明白了 - 但这需要在其他地方存储这些数据 - 很有趣。谢谢你花时间帮忙。 - Dave
1
这对于源代码/文档来说还不错,但对于大型二进制文件来说速度确实不够快。 - Robbie Dee
1
这取决于你如何定义“足够快”- 对于每周或每晚在空闲时间进行的无人值守备份过程,即使处理100GB的数据也可以在合理的时间内完成。我喜欢在受控环境中使用归档位解决方案,但根据我的备份过程运行的位置,我会对其信任感到担忧。 - Mike Marynowski
显示剩余2条评论

21

你可以通过它们的哈希值来比较文件:

private byte[] GetFileHash(string fileName)
{
    HashAlgorithm sha1 = HashAlgorithm.Create();
    using(FileStream stream = new FileStream(fileName,FileMode.Open,FileAccess.Read))
      return sha1.ComputeHash(stream);
}

如果内容被更改,哈希值将会不同。


+1 感谢您的代码。这似乎非常简单明了,很好地比较了末尾的2个字节。答案不错,谢谢。 - Dave
2
仅比较最后两个字节是不够的。使用 hash1.SequenceEqual(hash2) 来比较所有字节。 - Sergey Berezovskiy
2个字节是源地址和目标地址。 - Dave

12

你可能会喜欢查看FileSystemWatcher类。

"该类允许您监视目录的更改,并在文件被修改时触发事件。"

然后你的代码可以处理事件并处理文件。

代码来源 - MSDN:

// Create a new FileSystemWatcher and set its properties.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = args[1];

/* Watch for changes in LastAccess and LastWrite times, and
   the renaming of files or directories. */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
   | NotifyFilters.FileName | NotifyFilters.DirectoryName;

// Only watch text files.
watcher.Filter = "*.txt";

// Add event handlers.
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);

1
我的程序不是设计为24/7监视文件夹,只在复制/粘贴时即时检查2个文件。所以这是很好的信息和有用的替代方案,但我正在寻找比较2个文件。 - Dave
1
FYI,这似乎不是一个兼容Mono的解决方案。 - joelc
我查看源代码,发现有一个while循环在不断运行。这样做会不会让处理器繁忙或者产生额外开销?操作系统是如何管理这个问题的? - Omar Faroque Anik
@OmarFaroqueAnik - 线程是指进程在单独的线程中运行,操作系统通过选择要执行哪些线程、可以同时处理哪些内容以及不能处理什么内容来处理此过程,同时决定在 I/O 点上执行什么。 - Paul Carlton

1
一般而言,你可以让操作系统负责跟踪文件是否已更改。
如果你使用:
File.GetAttributes

检查存档标志,这将告诉您文件自上次归档以来是否已更改。我相信XCOPY和类似的软件在完成复制后会重置此标志,但您可能需要自己处理它。

您可以在DOS中轻松测试该标志:

dir /aa yourfilename

或者只需在Windows资源管理器中添加属性列。


1

文件存档标志通常由备份程序用于检查文件是否需要备份。当Windows修改或创建文件时,它会设置存档标志(请参见此处)。检查存档标志是否已设置以决定文件是否需要备份:

if ((File.GetAttributes(fileName) & FileAttributes.Archive) == FileAttributes.Archive)
{
    // Archive file.
}

备份文件后,清除存档标志:

File.SetAttributes(fileName, File.GetAttributes(fileName) & ~FileAttributes.Archive);

这假设没有其他程序(例如系统备份软件)清除存档标志。


0
从这篇文章中获取Crc32类在C#和.NET中计算CRC-32 将文件路径传递给此函数...它会返回一个CRC值...将其与已经存在的文件进行比较...如果CRC不同,则文件已更改。
internal Int32 GetCRC(string filepath)
{
    Int32 ret = 0;
    StringBuilder hash = new StringBuilder();
    try
    {
        Crc32 crc32 = new Crc32();
                
        using (System.IO.FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.None))
            foreach (byte b in crc32.ComputeHash(fs)) hash.Append(b.ToString("x2").ToLower());
                
        ret = Int32.Parse(hash.ToString(), System.Globalization.NumberStyles.HexNumber);
    }
    catch (Exception ex)
    {
        string msg = (ex.InnerException == null) ? ex.Message : ex.InnerException.Message;
        Console.WriteLine($"FILE ERROR: {msg}");
        
        ret = 0;
    }
    finally
    {
        hash.Clear();
        hash = null;
    }
            
    return ret;
}

另一个名称完全相同且内容正确的文件,可能会产生完全相同的CRC。 - Fandango68
1
哈哈... 这是真的。 - ecklerpa

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