.NET根據文件的哈希值篩選重復文件的實現(xiàn)思路
如題。先介紹下概念。
Q1. 文件的哈希值是什么?
文件的哈希值是通過特定的哈希算法對文件內容進行計算后得到的固定長度的字符串(通常由數(shù)字和字母組成),它具有以下特點和相關信息:
- 哈希算法:常見的用于計算文件哈希值的算法包括 MD5、SHA-1、SHA-256、SHA-512 等。不同算法的計算方式和生成的哈希值長度不同。例如,MD5 算法生成的哈希值是 128 位(通常以 32 個十六進制字符表示),SHA-256 算法生成的哈希值是 256 位(以 64 個十六進制字符表示) 。
- 唯一性:理論上,不同內容的文件經過哈希算法計算后,應該得到不同的哈希值。這意味著如果兩個文件的哈希值相同,那么它們的內容很可能是完全一樣的(雖然存在極小概率的哈希碰撞,即不同內容產生相同哈希值,但這種情況非常罕見)。例如,在文件傳輸過程中,接收方可以計算接收到的文件的哈希值,并與發(fā)送方提供的原始文件哈希值進行比較,以驗證文件在傳輸過程中是否完整無損,沒有被篡改。
- 固定長度:無論文件的大小和內容如何,使用特定哈希算法計算得到的哈希值長度是固定的。例如,無論一個文本文件是幾 KB 還是幾 MB,使用 SHA-256 算法計算出的哈希值始終是 256 位。
- 不可逆性:哈希計算是一種單向操作,即可以通過文件內容計算出哈希值,但無法從哈希值反向推導出原始的文件內容。這使得哈希值在數(shù)據存儲和安全驗證等方面具有重要作用,比如在存儲用戶密碼時,通常存儲的是密碼的哈希值,即使數(shù)據庫被攻擊,攻擊者也難以從哈希值還原出原始密碼。
哈希值在文件驗證、數(shù)據完整性檢查、文件去重、密碼存儲等眾多領域都有廣泛應用,是確保數(shù)據一致性和安全性的重要手段之一。
Q2. 比如一個文件 有1G大小,那么它的哈希值會多大?
文件哈希值的大小與文件本身的大小無關,而是由所使用的哈希算法決定。
常見的哈希算法及其生成的哈希值大小如下:
- MD5 算法:生成 128 位(16 字節(jié))的哈希值,通常以 32 個十六進制字符表示。例如:
d41d8cd98f00b204e9800998ecf8427e,無論原文件是 1 字節(jié)還是 1GB 甚至更大,使用 MD5 算法計算出的哈希值固定是 128 位。 - SHA-1 算法:產生 160 位(20 字節(jié))的哈希值,一般以 40 個十六進制字符呈現(xiàn)。比如:
da39a3ee5e6b4b0d3255bfef95601890afd80709,不管文件大小怎樣變化,SHA-1 算法得到的哈希值長度不變。SHA-256 算法:輸出 256 位(32 字節(jié))的哈希值,以 64 個十六進制字符展示。例如:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,即使是 1GB 的大文件,其 SHA-256 哈希值依然是 256 位。 - SHA-512 算法:生成 512 位(64 字節(jié))的哈希值,用 128 個十六進制字符表示。如:
cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e,同樣不受文件大小影響。
所以,一個 1GB 大小的文件,其哈希值的大小取決于選擇的哈希算法,而不是文件自身的體積大小。
需求:客戶有200+份文件來源于不同的三方平臺,但是客戶不確定是否有重復文件??赡軄碓床煌俏募嗤?蛻粝MY出這些重復文件。供用戶搜索用,這樣用戶就只會搜出唯一的文件結果,而不是來源于不同渠道的相同文件。
實現(xiàn)思路:
先獲取所有文件的大小,并按照大小group by,這樣相同大小的文件就會在一組。文件大小不同一定不屬于相同文件,文件大小相同有可能屬于相同文件,也有可能是不同的文件剛好大小相等。按照這方式,遍歷所有相同大小的組。比對哈希值。文件大小不同的就不用比較了??隙ú皇窍嗤募?。
/*
代碼說明
FindDuplicateFilesGrouped 方法:
首先遍歷傳入的文件路徑數(shù)組,將每個文件的信息(Id、FileName、FileSize)封裝到 FileInfoEntity 對象中,并添加到 fileInfos 列表。
接著按文件大小對 fileInfos 進行分組,得到 sizeGroups。
針對每個大小組,計算組內每個文件的哈希值,把具有相同哈希值的文件存到 hashGroups 字典里。
最后,將 hashGroups 中包含多個文件的組添加到 duplicateFileGroups 列表,該列表即為最終按組返回的重復文件結果。
通過這種方式,就能方便地找出所有重復文件,并將它們按組分類返回。
*/
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
{
// 文件實體類
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
{
// 獲取當前文件夾下的所有文件
string[] files = Directory.GetFiles(folderPath);
// 獲取當前文件夾下的所有子文件夾
string[] subFolders = Directory.GetDirectories(folderPath);
foreach (string subFolder in subFolders)
{
// 遞歸調用獲取子文件夾下的所有文件
string[] subFiles = GetAllFilesInFolder(subFolder);
files = files.Concat(subFiles).ToArray();
}
return files;
}
catch (Exception ex)
{
Console.WriteLine($"發(fā)生錯誤: {ex.Message}");
return new string[0];
}
}
// 計算文件哈希值
private static string CalculateHash(Stream stream)
{
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(stream);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
// 入參是兩個文件流,比較兩個文件
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;
}
// 入參是兩個文件路徑,比較兩個文件
public static bool CompareFiles(string filePath1, string filePath2)
{
using (FileStream stream1 = File.OpenRead(filePath1))
using (FileStream stream2 = File.OpenRead(filePath2))
{
return CompareFiles(stream1, stream2);
}
}
// 入參是文件路徑的數(shù)組,找出重復文件
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ù)組,將每個文件的信息(Id、FileName、FileSize)封裝到 FileInfoEntity 對象中,并添加到 fileInfos 列表。
接著按文件大小對 fileInfos 進行分組,得到 sizeGroups。
針對每個大小組,計算組內每個文件的哈希值,把具有相同哈希值的文件存到 hashGroups 字典里。
最后,將 hashGroups 中包含多個文件的組添加到 duplicateFileGroups 列表,該列表即為最終按組返回的重復文件結果。
通過這種方式,就能方便地找出所有重復文件,并將它們按組分類返回。
*/
// 入參是文件路徑數(shù)組,按組返回重復文件
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($"找到的重復文件組數(shù)量: {duplicateFileGroups.Count}");
foreach (var group in duplicateFileGroups)
{
Console.WriteLine($"該組重復文件數(shù)量: {group.Count}");
foreach (var file in group)
{
Console.WriteLine($" 文件 ID: {file.Id}, 文件名: {file.FileName}, 文件大小: {file.FileSize}");
}
}實現(xiàn)效果:

測試文件.txt文本的舉例子。可以看到黃框和紅框內的都屬于相同文件。紅框和黃框內的雖然字節(jié)數(shù)是一樣的,相同大小會group by到一組。但是hash值肯定是不同的。

到此這篇關于.NET下根據文件的哈希值篩選重復文件的文章就介紹到這了,更多相關.net哈希值篩選重復文件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
asp.net實現(xiàn)XML文件讀取數(shù)據綁定到DropDownList的方法
這篇文章主要介紹了asp.net實現(xiàn)XML文件讀取數(shù)據綁定到DropDownList的方法,結合實例形式分析了asp.net針對xml文件操作及DropDownList控件的使用技巧,需要的朋友可以參考下2017-02-02
未處理的事件"PageIndexChanging" 之解決方案
今天我寫一個小程序遇到這個問題,上網搜了一下,已經有很好的解決方法了,以前都是拉控件自己生成,現(xiàn)在用代碼自己寫就出現(xiàn)了這個問題2008-07-07

