Как получить уникальный идентификатор файла из файла
Прежде чем пометить этот вопрос как дубликат, пожалуйста, прочитайте то, что я пишу. Я проверил много вопросов на многих страницах для решения, но не смог ничего найти. В моем текущем приложении я использовал это:
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 секунд, чтобы получить идентификатор файла.
Интересно, есть ли другой способ получить что-то уникальное из файла с использованием хеширования или потока или без него? Моя целевая машина не NTFS или Windows все время, поэтому я должен найти другой путь.
Мне было интересно, имеет ли смысл, если я просто получу первое "х" количество байтов из потока и выполню хеширование для уникального идентификатора с этим потоком уменьшенного размера?
РЕДАКТИРОВАТЬ: Это не для безопасности или чего-то еще, мне нужен этот уникальный идентификатор, потому что FileSystemWatcher не работает :)
РЕДАКТИРОВАТЬ 2: На основе комментариев я решил обновить свой вопрос. Причина, по которой я это делаю, может быть, есть решение, которое не основано на создании уникального идентификатора для файла. Моя проблема в том, что я должен наблюдать за папкой и запускать события, когда они есть; A) Недавно добавленные файлы B) Измененные файлы C) Удаленные файлы
Причина, по которой я не могу использовать FileSystemWatcher, заключается в том, что она не надежна. Иногда я помещаю файл 100x в папку, а FileSystemWatcher запускает только события 20x-30x, и если это сетевой диск, иногда он может быть ниже. Мой метод состоял в сохранении всех файлов и их уникального идентификатора в текстовый файл и проверял индексный файл каждые 5 секунд, если есть какие-либо изменения. Если нет больших файлов, таких как 18 ГБ, он работает нормально.. Но вычисление хеш файла 40 ГБ занимает слишком много времени. Мой вопрос: как я могу запускать события, когда что-то происходит с папкой, которую я наблюдаю
РЕДАКТИРОВАТЬ 3: После установки награды я понял, что мне нужно дать больше информации о том, что происходит в моем коде. Сначала это мой ответ пользователю @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 в этом case оба из моего списка счетчика будет больше нуля, так что в противном случае срабатывает. Внутри остального я уже знаю хешированное значение (оно внутри моего текстового файла, которое я создал 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 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 ----------");
}
}
Ответы
Ответ 1
Что касается вашего подхода
- Нет никакой гарантии, что контрольные суммы (криптографические или не) столкновения можно избежать, независимо от того, насколько маловероятно.
- Чем больше вы обрабатываете файл, тем меньше вероятность.
- IO непрерывного разбора файлов невероятно дорог.
- Windows знает, когда файлы меняются, поэтому лучше использовать предоставленный механизм мониторинга.
FileSystemWatcher имеет буфер, его размер по умолчанию составляет 8192, минимум 4 КБ, максимум 64 КБ. Когда пропущены события, это обычно (только по моему опыту), потому что размер буфера слишком мал. Пример кода приведен ниже. В моем тесте я поместил 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
Ответ 2
Вы можете рассмотреть возможность использования контрольных сумм CRC, которые работают намного быстрее.
Вот как вычислить контрольный код 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, чтобы убедиться, что они совпадают или нет.
PS Также проверьте этот ответ для получения дополнительной информации о контрольных суммах против обычных хеш-кодов.