.NET根據(jù)文件的哈希值篩選重復(fù)文件的實(shí)現(xiàn)思路
如題。先介紹下概念。
Q1. 文件的哈希值是什么?
文件的哈希值是通過特定的哈希算法對(duì)文件內(nèi)容進(jìn)行計(jì)算后得到的固定長(zhǎng)度的字符串(通常由數(shù)字和字母組成),它具有以下特點(diǎn)和相關(guān)信息:
- 哈希算法:常見的用于計(jì)算文件哈希值的算法包括 MD5、SHA-1、SHA-256、SHA-512 等。不同算法的計(jì)算方式和生成的哈希值長(zhǎng)度不同。例如,MD5 算法生成的哈希值是 128 位(通常以 32 個(gè)十六進(jìn)制字符表示),SHA-256 算法生成的哈希值是 256 位(以 64 個(gè)十六進(jìn)制字符表示) 。
- 唯一性:理論上,不同內(nèi)容的文件經(jīng)過哈希算法計(jì)算后,應(yīng)該得到不同的哈希值。這意味著如果兩個(gè)文件的哈希值相同,那么它們的內(nèi)容很可能是完全一樣的(雖然存在極小概率的哈希碰撞,即不同內(nèi)容產(chǎn)生相同哈希值,但這種情況非常罕見)。例如,在文件傳輸過程中,接收方可以計(jì)算接收到的文件的哈希值,并與發(fā)送方提供的原始文件哈希值進(jìn)行比較,以驗(yàn)證文件在傳輸過程中是否完整無損,沒有被篡改。
- 固定長(zhǎng)度:無論文件的大小和內(nèi)容如何,使用特定哈希算法計(jì)算得到的哈希值長(zhǎng)度是固定的。例如,無論一個(gè)文本文件是幾 KB 還是幾 MB,使用 SHA-256 算法計(jì)算出的哈希值始終是 256 位。
- 不可逆性:哈希計(jì)算是一種單向操作,即可以通過文件內(nèi)容計(jì)算出哈希值,但無法從哈希值反向推導(dǎo)出原始的文件內(nèi)容。這使得哈希值在數(shù)據(jù)存儲(chǔ)和安全驗(yàn)證等方面具有重要作用,比如在存儲(chǔ)用戶密碼時(shí),通常存儲(chǔ)的是密碼的哈希值,即使數(shù)據(jù)庫被攻擊,攻擊者也難以從哈希值還原出原始密碼。
哈希值在文件驗(yàn)證、數(shù)據(jù)完整性檢查、文件去重、密碼存儲(chǔ)等眾多領(lǐng)域都有廣泛應(yīng)用,是確保數(shù)據(jù)一致性和安全性的重要手段之一。
Q2. 比如一個(gè)文件 有1G大小,那么它的哈希值會(huì)多大?
文件哈希值的大小與文件本身的大小無關(guān),而是由所使用的哈希算法決定。
常見的哈希算法及其生成的哈希值大小如下:
- MD5 算法:生成 128 位(16 字節(jié))的哈希值,通常以 32 個(gè)十六進(jìn)制字符表示。例如:
d41d8cd98f00b204e9800998ecf8427e
,無論原文件是 1 字節(jié)還是 1GB 甚至更大,使用 MD5 算法計(jì)算出的哈希值固定是 128 位。 - SHA-1 算法:產(chǎn)生 160 位(20 字節(jié))的哈希值,一般以 40 個(gè)十六進(jìn)制字符呈現(xiàn)。比如:
da39a3ee5e6b4b0d3255bfef95601890afd80709
,不管文件大小怎樣變化,SHA-1 算法得到的哈希值長(zhǎng)度不變。SHA-256 算法:輸出 256 位(32 字節(jié))的哈希值,以 64 個(gè)十六進(jìn)制字符展示。例如:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
,即使是 1GB 的大文件,其 SHA-256 哈希值依然是 256 位。 - SHA-512 算法:生成 512 位(64 字節(jié))的哈希值,用 128 個(gè)十六進(jìn)制字符表示。如:
cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
,同樣不受文件大小影響。
所以,一個(gè) 1GB 大小的文件,其哈希值的大小取決于選擇的哈希算法,而不是文件自身的體積大小。
需求:客戶有200+份文件來源于不同的三方平臺(tái),但是客戶不確定是否有重復(fù)文件。可能來源不同但是文件相同??蛻粝MY出這些重復(fù)文件。供用戶搜索用,這樣用戶就只會(huì)搜出唯一的文件結(jié)果,而不是來源于不同渠道的相同文件。
實(shí)現(xiàn)思路:
先獲取所有文件的大小,并按照大小group by,這樣相同大小的文件就會(huì)在一組。文件大小不同一定不屬于相同文件,文件大小相同有可能屬于相同文件,也有可能是不同的文件剛好大小相等。按照這方式,遍歷所有相同大小的組。比對(duì)哈希值。文件大小不同的就不用比較了??隙ú皇窍嗤募?。
/*
代碼說明
FindDuplicateFilesGrouped 方法:
首先遍歷傳入的文件路徑數(shù)組,將每個(gè)文件的信息(Id、FileName、FileSize)封裝到 FileInfoEntity 對(duì)象中,并添加到 fileInfos 列表。
接著按文件大小對(duì) fileInfos 進(jìn)行分組,得到 sizeGroups。
針對(duì)每個(gè)大小組,計(jì)算組內(nèi)每個(gè)文件的哈希值,把具有相同哈希值的文件存到 hashGroups 字典里。
最后,將 hashGroups 中包含多個(gè)文件的組添加到 duplicateFileGroups 列表,該列表即為最終按組返回的重復(fù)文件結(jié)果。
通過這種方式,就能方便地找出所有重復(fù)文件,并將它們按組分類返回。
*/
FileComparisonTool.cs
//FileComparisonTool.cs using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using System.IO; using System.Linq; using System.Security.Cryptography; namespace ConsoleApp1_CompareFiles { // 文件實(shí)體類 public class FileInfoEntity { public Guid Id { get; set; } public MemoryStream Stream { get; set; } public string FileName { get; set; } public long FileSize { get; set; } } // 文件比較工具類 public class FileComparisonTool { public static string[] GetAllFilesInFolder(string folderPath) { try { // 獲取當(dāng)前文件夾下的所有文件 string[] files = Directory.GetFiles(folderPath); // 獲取當(dāng)前文件夾下的所有子文件夾 string[] subFolders = Directory.GetDirectories(folderPath); foreach (string subFolder in subFolders) { // 遞歸調(diào)用獲取子文件夾下的所有文件 string[] subFiles = GetAllFilesInFolder(subFolder); files = files.Concat(subFiles).ToArray(); } return files; } catch (Exception ex) { Console.WriteLine($"發(fā)生錯(cuò)誤: {ex.Message}"); return new string[0]; } } // 計(jì)算文件哈希值 private static string CalculateHash(Stream stream) { using (SHA256 sha256 = SHA256.Create()) { byte[] hashBytes = sha256.ComputeHash(stream); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } // 入?yún)⑹莾蓚€(gè)文件流,比較兩個(gè)文件 public static bool CompareFiles(Stream stream1, Stream stream2) { stream1.Position = 0; stream2.Position = 0; string hash1 = CalculateHash(stream1); string hash2 = CalculateHash(stream2); return hash1 == hash2; } // 入?yún)⑹莾蓚€(gè)文件路徑,比較兩個(gè)文件 public static bool CompareFiles(string filePath1, string filePath2) { using (FileStream stream1 = File.OpenRead(filePath1)) using (FileStream stream2 = File.OpenRead(filePath2)) { return CompareFiles(stream1, stream2); } } // 入?yún)⑹俏募窂降臄?shù)組,找出重復(fù)文件 public static List<FileInfoEntity> FindDuplicateFiles(string[] filePaths) { var fileInfos = new List<FileInfoEntity>(); foreach (var filePath in filePaths) { var fileInfo = new FileInfo(filePath); fileInfos.Add(new FileInfoEntity { Id = Guid.NewGuid(), FileName = filePath, FileSize = fileInfo.Length }); } var groups = fileInfos.GroupBy(f => f.FileSize); var duplicateFiles = new List<FileInfoEntity>(); foreach (var group in groups) { if (group.Count() > 1) { var hashes = new Dictionary<string, FileInfoEntity>(); foreach (var file in group) { using (FileStream stream = File.OpenRead(file.FileName)) { string hash = CalculateHash(stream); if (hashes.ContainsKey(hash)) { if (!duplicateFiles.Contains(hashes[hash])) { duplicateFiles.Add(hashes[hash]); } duplicateFiles.Add(file); } else { hashes[hash] = file; } } } } } return duplicateFiles; } /* 代碼說明 FindDuplicateFilesGrouped 方法: 首先遍歷傳入的文件路徑數(shù)組,將每個(gè)文件的信息(Id、FileName、FileSize)封裝到 FileInfoEntity 對(duì)象中,并添加到 fileInfos 列表。 接著按文件大小對(duì) fileInfos 進(jìn)行分組,得到 sizeGroups。 針對(duì)每個(gè)大小組,計(jì)算組內(nèi)每個(gè)文件的哈希值,把具有相同哈希值的文件存到 hashGroups 字典里。 最后,將 hashGroups 中包含多個(gè)文件的組添加到 duplicateFileGroups 列表,該列表即為最終按組返回的重復(fù)文件結(jié)果。 通過這種方式,就能方便地找出所有重復(fù)文件,并將它們按組分類返回。 */ // 入?yún)⑹俏募窂綌?shù)組,按組返回重復(fù)文件 public static List<List<FileInfoEntity>> FindDuplicateFilesGrouped(string[] filePaths) { var fileInfos = new List<FileInfoEntity>(); foreach (var filePath in filePaths) { var fileInfo = new FileInfo(filePath); fileInfos.Add(new FileInfoEntity { Id = Guid.NewGuid(), FileName = filePath, FileSize = fileInfo.Length }); } var sizeGroups = fileInfos.GroupBy(f => f.FileSize); var duplicateFileGroups = new List<List<FileInfoEntity>>(); foreach (var sizeGroup in sizeGroups) { if (sizeGroup.Count() > 1) { var hashGroups = new Dictionary<string, List<FileInfoEntity>>(); foreach (var file in sizeGroup) { using (FileStream stream = File.OpenRead(file.FileName)) { string hash = CalculateHash(stream); if (!hashGroups.ContainsKey(hash)) { hashGroups[hash] = new List<FileInfoEntity>(); } hashGroups[hash].Add(file); } } foreach (var hashGroup in hashGroups.Values) { if (hashGroup.Count > 1) { duplicateFileGroups.Add(hashGroup); } } } } return duplicateFileGroups; } } }
//
// Program.cs // See https://aka.ms/new-console-template for more information using ConsoleApp1_CompareFiles; Console.WriteLine("Hello, World!"); string[] filePaths = { "C:\\Users\\Aa\\Desktop\\新建文件夾\\1.txt", "C:\\Users\\Aa\\Desktop\\新建文件夾\\2.txt", "C:\\Users\\Aa\\Desktop\\新建文件夾\\3.txt", "C:\\Users\\Aa\\Desktop\\新建文件夾\\4.xlsx", "C:\\Users\\Aa\\Desktop\\新建文件夾\\5.xlsx", "C:\\Users\\Aa\\Desktop\\新建文件夾\\6.docx", "C:\\Users\\Aa\\Desktop\\新建文件夾\\7.docx", "C:\\Users\\Aa\\Desktop\\新建文件夾\\8.jpeg", "C:\\Users\\Aa\\Desktop\\新建文件夾\\9.jpeg", "C:\\Users\\Aa\\Desktop\\新建文件夾\\捕獲 - 副本.PNG", "C:\\Users\\Aa\\Desktop\\新建文件夾\\捕獲.PNG", "C:\\Users\\Aa\\Desktop\\新建文件夾\\微信圖片_20250221165428.png", }; string folderPath = @"C:\Users\Aa\Desktop\新建文件夾"; string[] allFiles = FileComparisonTool.GetAllFilesInFolder(folderPath); var duplicateFileGroups = FileComparisonTool.FindDuplicateFilesGrouped(allFiles); Console.WriteLine($"找到的重復(fù)文件組數(shù)量: {duplicateFileGroups.Count}"); foreach (var group in duplicateFileGroups) { Console.WriteLine($"該組重復(fù)文件數(shù)量: {group.Count}"); foreach (var file in group) { Console.WriteLine($" 文件 ID: {file.Id}, 文件名: {file.FileName}, 文件大小: {file.FileSize}"); } }
實(shí)現(xiàn)效果:
測(cè)試文件.txt文本的舉例子??梢钥吹近S框和紅框內(nèi)的都屬于相同文件。紅框和黃框內(nèi)的雖然字節(jié)數(shù)是一樣的,相同大小會(huì)group by到一組。但是hash值肯定是不同的。
到此這篇關(guān)于.NET下根據(jù)文件的哈希值篩選重復(fù)文件的文章就介紹到這了,更多相關(guān).net哈希值篩選重復(fù)文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
收集學(xué)習(xí)asp.net比較完整的面向?qū)ο箝_發(fā)流程
如果你已經(jīng)有較多的面向?qū)ο箝_發(fā)經(jīng)驗(yàn),跳過以下這兩步 第一步:掌握一門.NET面向?qū)ο笳Z言第二步:對(duì).NET Framework類庫有一定的了解;在具備了OO基礎(chǔ)之后,以下是具體的學(xué)習(xí)ASP.NET技術(shù)步驟2012-12-12asp.net實(shí)現(xiàn)XML文件讀取數(shù)據(jù)綁定到DropDownList的方法
這篇文章主要介紹了asp.net實(shí)現(xiàn)XML文件讀取數(shù)據(jù)綁定到DropDownList的方法,結(jié)合實(shí)例形式分析了asp.net針對(duì)xml文件操作及DropDownList控件的使用技巧,需要的朋友可以參考下2017-02-02未處理的事件"PageIndexChanging" 之解決方案
今天我寫一個(gè)小程序遇到這個(gè)問題,上網(wǎng)搜了一下,已經(jīng)有很好的解決方法了,以前都是拉控件自己生成,現(xiàn)在用代碼自己寫就出現(xiàn)了這個(gè)問題2008-07-07WPF實(shí)現(xiàn)帶全選復(fù)選框的列表控件
這篇文章主要為大家詳細(xì)介紹了WPF實(shí)現(xiàn)帶全選復(fù)選框的列表控件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06解析ABP框架中的數(shù)據(jù)傳輸對(duì)象與應(yīng)用服務(wù)
ABP框架是基于ASP.NET的Web開發(fā)框架,在ABP中應(yīng)用服務(wù)將領(lǐng)域邏輯暴露給展現(xiàn)層,展現(xiàn)層通過傳入數(shù)據(jù)傳輸對(duì)象參數(shù)來調(diào)用應(yīng)用服務(wù),而這里我們就來解析ABP框架中的數(shù)據(jù)傳輸對(duì)象與應(yīng)用服務(wù)2016-06-06