如何从文件中获取唯一的文件标识符

时间:2019-03-12 10:43:43

标签: c#

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

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)保存在文本文件中,每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如果我的列表的两个计数都将大于零,那么将被触发。在其他内部,我已经知道一个哈希值(它在我5秒钟前创建的文本文件中),现在我将其与所有AddedList元素进行比较,看看是否可以匹配它们,如果没有匹配项,那就是重命名情况匹配,那么我可以说自上次扫描以来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 ----------");
            }
        }

2 个答案:

答案 0 :(得分:8)

关于您的方法

  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

答案 1 :(得分:0)

您可以考虑使用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 /这样的哈希值那么独特。...
因此,在有许多文件的情况下,您可以考虑设计校验和和散列的一些 hybrid 解决方案。可能的解决方案可能是先计算校验和,如果它们匹配,则仅计算MD5以确保它们相同或不相同。

P.S。另外,请检查this答案以了解有关校验和与常规哈希码的更多信息。