【问题标题】:How to get unique file identifier from a file如何从文件中获取唯一的文件标识符
【发布时间】:2019-08-02 19:27:47
【问题描述】:

在将此问题标记为重复之前,请阅读我写的内容。 我已经在很多页面中检查了许多问题以找到解决方案,但找不到任何东西。 在我当前的应用程序中,我正在使用这个:

using (var md5 = MD5.Create())
{
    using (FileStream stream = File.OpenRead(FilePath))
    {
        var hash = md5.ComputeHash(stream);
        var cc = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        Console.WriteLine("Unique ID  : " + cc);
    }
}

这对我来说对于小文件来说已经足够好了,但是一旦我对大文件进行尝试,我需要大约 30-60 秒才能获得文件 ID。

我想知道是否有任何其他方法可以使用或不使用散列或流从文件中获取独特的东西?我的目标机器一直不是 NTFS 或 windows,所以我必须找到另一种方法。

我想知道如果我只是从流中获取前“x”个字节并使用该减小大小的流对唯一 ID 进行哈希处理是否有意义?

编辑:这不是为了安全或其他任何事情,我需要这个唯一 ID,因为 FileSystemWatcher 不工作:)

EDIT2:基于 cmets,我决定更新我的问题。我这样做的原因可能不是基于为文件创建唯一 ID 的解决方案。 我的问题是我必须观看文件夹并在有事件时触发事件; A) 新添加的文件 B) 更改的文件 C) 删除的文件

我不能使用 FileSystemWatcher 的原因是它不可靠。有时我将 100x 文件放入文件夹,而 FileSystemWatcher 仅触发 20x-30x 事件,如果它是网络驱动器,有时它可能会更低。 我的方法是将所有文件及其唯一 ID 保存到一个文本文件中,如果有任何更改,则每 5 秒检查一次索引文件。如果没有像 18GB 这样的大文件,它工作正常。但是计算 40GB 文件的哈希值太长了。 我的问题是:当我正在观看的文件夹发生问题时,如何触发事件

EDIT3:设置赏金后,我意识到我需要提供更多关于我的代码中发生了什么的信息。首先,这是我对用户@JustShadow 的回答(太长了,我无法将其作为评论发送) 我将解释我是如何做到的,我将 filepath-uniqueID(MD5 hashed) 保存在文本文件中,每 5 秒我使用 Directory.GetFiles(DirectoryPath); 检查文件夹。 然后我将我的第一个列表与 5 秒前的列表进行比较,这样我得到了 2 个列表

List<string> AddedList = FilesInFolder.Where(x => !OldList.Contains(x)).ToList();
List<string> RemovedList = OldList.Where(x => !FilesInFolder.Contains(x)).ToList();

这就是我得到它们的方式。现在我有了我的 if 块,

if (AddedList.Count &gt; 0 &amp;&amp; RemovedList.Count == 0) 那么很高兴没有重命名新文件。我散列所有新文件并将它们添加到我的文本文件中。

if (AddedList.Count == 0 && RemovedList.Count > 0)

如果仍然不错,则与第一个相反,只有已删除的项目,我将它们从该项目的文本文件中删除并完成。 在这种情况之后出现了我的 else 块.. 这是我进行比较的地方,基本上我对所有添加和删除的列表项进行哈希处理,然后我将两个列表中都存在的项作为示例 a.txt 在此重命名为 b.txt如果我的列表的两个计数都大于零,因此会触发其他情况。在 else 里面我已经知道 a 的散列值(它在我 5 秒前创建的文本文件中)现在我将它与所有 AdditionalList 元素进行比较,看看如果我得到匹配,我是否可以匹配它们,如果没有,那就是重命名情况匹配然后我可以说 b.txt 自上次扫描以来确实新添加到列表中。 我还将提供一些我的课程代码,所以也许有办法解决这个谜题。

现在我还将分享一些我的课程代码,也许当每个人都知道我在做什么时,我们可以找到解决它的方法。 这就是我的计时器的样子

private void TestTmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {

            lock (locker)
            {
                if (string.IsNullOrWhiteSpace(FilePath))
                {
                    Console.WriteLine("Timer will be return because FilePath is empty. --> " + FilePath);
                    return;
                }
                try
                {
                    if (!File.Exists(FilePath + @"\index.MyIndexFile"))
                    {
                        Console.WriteLine("File not forund. Will be created now.");
                        FileStream close = File.Create(FilePath + @"\index.MyIndexFile");
                        close.Close();
                        return;
                    }

                    string EncryptedText = File.ReadAllText(FilePath + @"\index.MyIndexFile");
                    string JsonString = EncClass.Decrypt(EncryptedText, "SecretPassword");
                    CheckerModel obj = Newtonsoft.Json.JsonConvert.DeserializeObject<CheckerModel>(JsonString);
                    if (obj == null)
                    {
                        CheckerModel check = new CheckerModel();
                        FileInfo FI = new FileInfo(FilePath);
                        check.LastCheckTime = FI.LastAccessTime.ToString();
                        string JsonValue = Newtonsoft.Json.JsonConvert.SerializeObject(check);

                        if (!File.Exists(FilePath + @"\index.MyIndexFile"))
                        {
                            FileStream GG = File.Create(FilePath + @"\index.MyIndexFile");
                            GG.Close();
                        }

                        File.WriteAllText(FilePath + @"\index.MyIndexFile", EncClass.Encrypt(JsonValue, "SecretPassword"));
                        Console.WriteLine("DATA FILLED TO TEXT FILE");
                        obj = Newtonsoft.Json.JsonConvert.DeserializeObject<CheckerModel>(JsonValue);
                    }
                    DateTime LastAccess = Directory.GetLastAccessTime(FilePath);
                    string[] FilesInFolder = Directory.GetFiles(FilePath, "*.*", SearchOption.AllDirectories);
                    List<string> OldList = new List<string>(obj.Files.Select(z => z.Path).ToList());

                    List<string> AddedList = FilesInFolder.Where(x => !OldList.Contains(x)).ToList();
                    List<string> RemovedList = OldList.Where(x => !FilesInFolder.Contains(x)).ToList();


                    if (AddedList.Count == 0 & RemovedList.Count == 0)
                    {
                        //no changes.
                        Console.WriteLine("Nothing changed since last scan..!");
                    }
                    else if (AddedList.Count > 0 && RemovedList.Count == 0)
                    {
                        Console.WriteLine("Adding..");
                        //Files added but removedlist is empty which means they are not renamed. Fresh added..
                        List<System.Windows.Forms.ListViewItem> LvItems = new List<System.Windows.Forms.ListViewItem>();
                        for (int i = 0; i < AddedList.Count; i++)
                        {
                            LvItems.Add(new System.Windows.Forms.ListViewItem(AddedList[i] + " has added since last scan.."));
                            FileModel FileItem = new FileModel();
                            using (var md5 = MD5.Create())
                            {
                                using (FileStream stream = File.OpenRead(AddedList[i]))
                                {
                                    FileItem.Size = stream.Length.ToString();
                                    var hash = md5.ComputeHash(stream);
                                    FileItem.Id = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                                }
                            }
                            FileItem.Name = Path.GetFileName(AddedList[i]);
                            FileItem.Path = AddedList[i];
                            obj.Files.Add(FileItem);
                        }
                    }
                    else if (AddedList.Count == 0 && RemovedList.Count > 0)
                    {
                        //Files removed and non has added which means files have deleted only. Not renamed.
                        for (int i = 0; i < RemovedList.Count; i++)
                        {
                            Console.WriteLine(RemovedList[i] + " has been removed from list since last scan..");
                            obj.Files.RemoveAll(x => x.Path == RemovedList[i]);
                        }
                    }
                    else
                    {
                        //Check for rename situations..

                        //Scan newly added files for MD5 ID's. If they are same with old one that means they are renamed.
                        //if a newly added file has a different MD5 ID that is not represented in old ones this file is fresh added.
                        for (int i = 0; i < AddedList.Count; i++)
                        {
                            string NewFileID = string.Empty;
                            string NewFileSize = string.Empty;
                            using (var md5 = MD5.Create())
                            {
                                using (FileStream stream = File.OpenRead(AddedList[i]))
                                {
                                    NewFileSize = stream.Length.ToString();
                                    var hash = md5.ComputeHash(stream);
                                    NewFileID = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                                }
                            }
                            FileModel Result = obj.Files.FirstOrDefault(x => x.Id == NewFileID);
                            if (Result == null)
                            {
                                //Not a rename. It's fresh file.
                                Console.WriteLine(AddedList[i] + " has added since last scan..");
                                //Scan new file and add it to the json list.

                            }
                            else
                            {
                                Console.WriteLine(Result.Path + " has renamed into --> " + AddedList[i]);
                                //if file is replaced then it should be removed from RemovedList
                                RemovedList.RemoveAll(x => x == Result.Path);
                                obj.Files.Remove(Result);
                                //After removing old one add new one. This way new one will look like its renamed
                                FileModel ModelToadd = new FileModel();
                                ModelToadd.Id = NewFileID;
                                ModelToadd.Name = Path.GetFileName(AddedList[i]);
                                ModelToadd.Path = AddedList[i];
                                ModelToadd.Size = NewFileSize;
                                obj.Files.Add(ModelToadd);
                            }

                        }

                        //After handle AddedList we should also inform user for removed files 
                        for (int i = 0; i < RemovedList.Count; i++)
                        {
                            Console.WriteLine(RemovedList[i] + " has deleted since last scan.");
                        }
                    }

                    //Update Json after checking everything.
                    obj.LastCheckTime = LastAccess.ToString();
                    File.WriteAllText(FilePath + @"\index.MyIndexFile", EncClass.Encrypt(Newtonsoft.Json.JsonConvert.SerializeObject(obj), "SecretPassword"));


                }
                catch (Exception ex)
                {
                    Console.WriteLine("ERROR : " + ex.Message);
                    Console.WriteLine("Error occured --> " + ex.Message);
                }
                Console.WriteLine("----------- END OF SCAN ----------");
            }
        }

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • @shino-lex,我在下面更新了我的答案。请再检查一遍。
  • 您在使用FileSystemWatcher时是否尝试关注these recommendations
  • @Spotted 是的,我已经在 FileSystemWatcher 端尝试了所有方法,但仍然相同,我收到此错误:目录中一次更改太多:xxx 即使我将 FSW 设置为仅捕获创建(甚至没有更改或任何东西)否则)具有最大缓冲区的事件。我只是粘贴了 1 个小 txt 文件进行测试(7-8kb),但仍然出现此错误
  • 可以重命名文件(在您的应用程序之外)吗?文件可以在一秒钟内更改多次吗?我正在考虑结合文件名和上次修改日期来检测更改。如果满足先决条件,我将发布一些涉及此的代码。

标签: c#


【解决方案1】:

关于你的方法

  1. 不保证可以避免校验和(加密或非)冲突,无论多么不可能。
  2. 处理文件的次数越多,可能性就越小。
  3. 不断解析文件的 IO 非常昂贵。
  4. Windows 知道文件何时发生变化,因此最好使用提供的监控机制。

FileSystemWatcher 有一个缓冲区,它的默认大小是 8192,最小 4KB,最大 64KB。当错过事件时,通常(仅根据我的经验)因为缓冲区太小。示例代码如下。在我的测试中,我将 296 个文件放入(空)C:\Temp 文件夹中。每个副本导致 3 个事件。没有遗漏。

using System;
using System.IO;
using System.Threading;

namespace FileSystemWatcherDemo
{
  class Program
  {
    private static volatile int Count = 0;
    private static FileSystemWatcher Fsw = new FileSystemWatcher
    {
      InternalBufferSize = 48 * 1024,  //  default 8192 bytes, min 4KB, max 64KB
      EnableRaisingEvents = false
    };
    private static void MonitorFolder(string path)
    {
      Fsw.Path = path;
      Fsw.Created += FSW_Add;
      Fsw.Created += FSW_Chg;
      Fsw.Created += FSW_Del;
      Fsw.EnableRaisingEvents = true;
    }

    private static void FSW_Add(object sender, FileSystemEventArgs e) { Console.WriteLine($"ADD: {++Count} {e.Name}"); }
    private static void FSW_Chg(object sender, FileSystemEventArgs e) { Console.WriteLine($"CHG: {++Count} {e.Name}"); }
    private static void FSW_Del(object sender, FileSystemEventArgs e) { Console.WriteLine($"DEL: {++Count} {e.Name}"); }
    static void Main(string[] args)
    {
      MonitorFolder(@"C:\Temp\");
      while (true)
      {
        Thread.Sleep(500);
        if (Console.KeyAvailable) break;
      }
      Console.ReadKey();  //  clear buffered keystroke
      Fsw.EnableRaisingEvents = false;
      Console.WriteLine($"{Count} file changes detected");
      Console.ReadKey();
    }
  }
}

结果

ADD: 880 tmpF780.tmp
CHG: 881 tmpF780.tmp
DEL: 882 tmpF780.tmp
ADD: 883 vminst.log
CHG: 884 vminst.log
DEL: 885 vminst.log
ADD: 886 VSIXbpo3w5n5.vsix
CHG: 887 VSIXbpo3w5n5.vsix
DEL: 888 VSIXbpo3w5n5.vsix
888 file changes detected

【讨论】:

  • 我的目标机器是 NAS(网络附加存储),并且提供商使用唯一的 Linux 文件系统,因此 FileSystemWatcher 在目录中一次抛出太多更改:xxx 错误,即使我将缓冲区设置为最大大小并且只设置创建事件。
  • @ShinoLex 然后我建议检查一些易于访问的快速属性/ies(如修改时间和大小),当任何似乎发生变化时,使用昂贵的计算属性(如MD5) 确认。不管散列有多好,它永远不能保证是唯一的(这就是为什么文件比较实用程序提供时间戳、CRC 等 - 作为最后的手段 - 二进制比较)。如果在很短的时间内可以更改数百个文件,那么每 5 秒读取每个文件以计算 MD5 很可能会影响您的网络和/或 NAS 性能。
  • 好的,那么我将使用混合系统,但我仍然无法管理如何做到这一点?我应该先获取两个文件的 CRC 结果并进行比较吗?如果它们相同,那么我应该使用文件大小,如果它们仍然相同,那么我应该使用 MD5 散列以确保它们是相同的文件?这会更好吗?我还不确定(我在写这篇评论时只是在想这个)但也许我可以在文件属性中设置一些东西?像视频文件元数据?我可以访问并为我扫描的文件提供唯一编号吗?所以我可以在下次扫描时到达该属性并检查它?
  • 如果文件大小改变了文件已经改变,所以不需要哈希/校验和,只需处理更改。否则,如果时间戳更改(相同大小)文件内容可能已更改,则可能使用校验和/哈希进行确认。据我所知,图像/视频标签存储在文件中,因此您将更改文件以对其进行监视。保留本地字典进行比较可能是要走的路。
  • 但我需要一些能与该文件保持一致的东西。如果我使用某种字典并自己制作表格,那么我无法匹配重命名。假设 5 秒后有 1.txt(下一次扫描),不再有 1.txt,而是有 2.txt,现在要知道它是重命名(1.txt-->2.txt)还是 1。 txt 删除并添加了一个名为 2.txt 的新文件 我需要文件中的内容吗?我在这里吗?如果我错了,请纠正我
【解决方案2】:

您可以考虑使用CRC 校验和,它的工作速度更快。
以下是使用 C# 计算 CRC64 校验和的方法:

Crc64 crc64 = new Crc64();
String hash = String.Empty;

using (FileStream fs = File.Open("c:\\myBigFile.raw", FileMode.Open))
  foreach (byte b in crc64.ComputeHash(fs)) hash += b.ToString("x2").ToLower();

Console.WriteLine("CRC-64 is {0}", hash);

这会在几秒钟内计算出我的 4GB 文件的校验和。

注意:
校验和不像 MD5/SHA/....这样的哈希那样唯一
因此,如果文件很多,您可能会考虑制作一些校验和和哈希的混合解决方案。可能的解决方案可能是先计算校验和,如果它们匹配,然后再计算 MD5 以确保它们是否相同。

附:另请查看this 答案以获取有关校验和与常用哈希码的更多信息。

【讨论】:

  • 我将编辑我的帖子并编写我的一些代码。也许我们可以用当前的代码以某种方式解决它?
  • 无论您使用何种算法计算基于内容的校验和,读取 40 GB 文件的速度都非常慢。
  • 同意。从存储中读取 40GB 文件肯定是一个缓慢的操作。但是这里说的快得多,我说的是针对 MD5/SHA/的计算速度 CRC...
  • 我可以使用能粘在文件上的东西吗?像元数据或属性?我的意思是按属性的属性详细信息页面。这确实会改变文件大小,但现在我的每个文件都有一个真正的唯一 ID?
  • @JustShadow 我想再次感谢你展示给我的方式。它启发了我。我已经在文件的末尾添加了我的唯一 ID,并使用我自己的唯一 ID 报告了文件,并发送了不带 ID 的显示名称。现在,如果文件被添加/删除或重命名,我可以清楚地分开。这可能不是一个完美的解决方案,但现在它正在工作。至少对我来说..
猜你喜欢
  • 1970-01-01
  • 2012-04-25
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 2021-10-30
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多