首页 > 解决方案 > 如何从文件中获取唯一的文件标识符

问题描述

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

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:根据评论,我决定更新我的问题。我这样做的原因可能不是基于为文件创建唯一 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 > 0 && 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 ----------");
            }
        }

标签: c#

解决方案


至于你的方法

  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

推荐阅读