在Visual Studio 2022开发C++程序上用广度优先算法,结合多线程和异步I/O遍历目录及其子目录,获取所有文件的路径、文件名称和内容大小。在遍历目录的同时,将其中内容大小相同文件归为一组。然后每组所有文件取它们的前100个字节,逐字节比较,取出其中结果相同的文件归为一组,计算每组内所有文件的SHA-256哈希值,哈希值相同的文件作为重复文件,输出到结果文本文件中。程序实现异常处理,程序运行的状态、运行开始和结束的时间戳、程序查找的目录、运行的时间和找到了重复文件的路径、文件名以及创建时间、修改时间、最后访问时间、权限和大小一起存储到日志文本文件里。
完整的C++代码解决方案实现结合了多线程并行处理、高效的哈希计算和详细的日志记录,能够有效处理大规模文件系统的重复文件检测需求,包含必要的注释和说明:
#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <unordered_map>
#include <windows.h>
#include <wincrypt.h>
#include <chrono>
#include <sstream>
#include <iomanip>
#include <atomic>
#include <algorithm>
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "Advapi32.lib")
using namespace std;
using namespace chrono;
// 数据结构存储文件信息
struct FileInfo {
wstring path;
wstring filename;
LARGE_INTEGER size;
FILETIME creationTime;
FILETIME lastWriteTime;
FILETIME lastAccessTime;
DWORD attributes;
};
// 全局数据结构
unordered_map<LONGLONG, vector<FileInfo>> sizeMap;
unordered_map<string, vector<FileInfo>> contentMap;
unordered_map<string, vector<FileInfo>> hashMap;
// 同步原语
mutex sizeMtx, contentMtx, hashMtx, logMtx, queueMtx;
condition_variable cv;
queue<wstring> dirQueue;
atomic<bool> done(false);
// 日志文件
ofstream logFile;
// 将FILETIME转换为SYSTEMTIME再转换为字符串
wstring FileTimeToString(FILETIME ft) {
SYSTEMTIME st;
FileTimeToSystemTime(&ft, &st);
wstringstream ss;
ss << st.wYear << "-" << setw(2) << setfill(L'0') << st.wMonth << "-"
<< setw(2) << st.wDay << " " << setw(2) << st.wHour << ":"
<< setw(2) << st.wMinute << ":" << setw(2) << st.wSecond;
return ss.str();
}
// 计算SHA-256哈希
string CalculateSHA256(const wstring& path) {
HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) return "";
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
BYTE buffer[4096];
DWORD bytesRead;
string result;
if (CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) &&
CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) {
while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, nullptr) && bytesRead > 0) {
CryptHashData(hHash, buffer, bytesRead, 0);
}
DWORD hashSize = 0;
DWORD dwSize = sizeof(DWORD);
CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&hashSize, &dwSize, 0);
vector<BYTE> hashBuffer(hashSize);
CryptGetHashParam(hHash, HP_HASHVAL, hashBuffer.data(), &hashSize, 0);
stringstream ss;
for (BYTE b : hashBuffer) {
ss << hex << setw(2) << setfill('0') << (int)b;
}
result = ss.str();
}
if (hHash) CryptDestroyHash(hHash);
if (hProv) CryptReleaseContext(hProv, 0);
CloseHandle(hFile);
return result;
}
// 日志记录函数
void Log(const wstring& message) {
lock_guard<mutex> lock(logMtx);
logFile << system_clock::to_time_t(system_clock::now()) << " - "
<< wstring_convert<codecvt_utf8<wchar_t>>().to_bytes(message) << endl;
}
// 目录遍历线程函数
void TraverseDirectories(const wstring& rootDir) {
queue<wstring> dirs;
dirs.push(rootDir);
while (!dirs.empty()) {
wstring currentDir = dirs.front();
dirs.pop();
WIN32_FIND_DATAW findData;
HANDLE hFind = FindFirstFileW((currentDir + L"\\*").c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0)
continue;
wstring fullPath = currentDir + L"\\" + findData.cFileName;
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
dirs.push(fullPath);
} else {
LARGE_INTEGER fileSize;
fileSize.LowPart = findData.nFileSizeLow;
fileSize.HighPart = findData.nFileSizeHigh;
FileInfo info{
fullPath,
findData.cFileName,
fileSize,
findData.ftCreationTime,
findData.ftLastWriteTime,
findData.ftLastAccessTime,
findData.dwFileAttributes
};
lock_guard<mutex> lock(sizeMtx);
sizeMap[fileSize.QuadPart].push_back(info);
}
} while (FindNextFileW(hFind, &findData));
FindClose(hFind);
}
}
done = true;
cv.notify_all();
}
// 比较文件内容前100字节
vector<FileInfo> CompareFileContents(const vector<FileInfo>& files) {
unordered_map<string, vector<FileInfo>> localContentMap;
for (const auto& file : files) {
ifstream f(file.path, ios::binary);
if (!f) continue;
vector<char> buffer(100);
f.read(buffer.data(), 100);
string content(buffer.begin(), buffer.begin() + f.gcount());
lock_guard<mutex> lock(contentMtx);
localContentMap[content].push_back(file);
}
vector<FileInfo> duplicates;
for (const auto& pair : localContentMap) {
if (pair.second.size() > 1) {
duplicates.insert(duplicates.end(), pair.second.begin(), pair.second.end());
}
}
return duplicates;
}
// 处理哈希计算的线程函数
void ProcessHash(const vector<FileInfo>& files) {
unordered_map<string, vector<FileInfo>> localHashMap;
for (const auto& file : files) {
string hash = CalculateSHA256(file.path);
if (!hash.empty()) {
localHashMap[hash].push_back(file);
}
}
lock_guard<mutex> lock(hashMtx);
for (const auto& pair : localHashMap) {
if (pair.second.size() > 1) {
hashMap.insert(pair);
}
}
}
int main() {
// 初始化日志文件
logFile.open("duplicates.log", ios::out | ios::trunc);
if (!logFile) {
wcerr << L"无法创建日志文件" << endl;
return 1;
}
auto startTime = system_clock::now();
Log(L"程序启动,开始时间: " + wstring_convert<codecvt_utf8<wchar_t>>().from_bytes(
system_clock::to_time_t(startTime)));
// 创建遍历线程
wstring rootDir = L"C:\\TargetDirectory"; // 修改为目标目录
thread traversalThread(TraverseDirectories, rootDir);
// 等待遍历完成
traversalThread.join();
// 处理相同大小的文件
vector<future<vector<FileInfo>>> contentFutures;
for (auto& pair : sizeMap) {
if (pair.second.size() > 1) {
contentFutures.push_back(async(launch::async, CompareFileContents, pair.second));
}
}
// 收集内容相同的文件
vector<vector<FileInfo>> contentGroups;
for (auto& future : contentFutures) {
auto group = future.get();
if (!group.empty()) {
contentGroups.push_back(group);
}
}
// 处理哈希计算
vector<future<void>> hashFutures;
for (auto& group : contentGroups) {
hashFutures.push_back(async(launch::async, ProcessHash, group));
}
// 等待所有哈希计算完成
for (auto& future : hashFutures) {
future.wait();
}
// 输出结果到文件
ofstream resultFile("duplicates.txt");
for (const auto& hashPair : hashMap) {
if (hashPair.second.size() > 1) {
resultFile << "Hash: " << hashPair.first << "\n";
for (const auto& file : hashPair.second) {
resultFile << " Path: " << wstring_convert<codecvt_utf8<wchar_t>>().to_bytes(file.path)
<< "\n Size: " << file.size.QuadPart
<< "\n Created: " << wstring_convert<codecvt_utf8<wchar_t>>().to_bytes(FileTimeToString(file.creationTime))
<< "\n Modified: " << wstring_convert<codecvt_utf8<wchar_t>>().to_bytes(FileTimeToString(file.lastWriteTime))
<< "\n\n";
}
}
}
auto endTime = system_clock::now();
auto duration = duration_cast<seconds>(endTime - startTime);
Log(L"程序完成,运行时间: " + to_wstring(duration.count()) + L"秒");
Log(L"找到重复文件组数: " + to_wstring(hashMap.size()));
logFile.close();
return 0;
}
代码说明和增强改进建议:
- 多线程架构:
- 使用独立的线程进行目录遍历
- 使用async/async进行并行内容比较和哈希计算
- 使用互斥锁保护共享数据结构
- 性能优化:
- 增加线程池管理(可使用线程池库)
- 实现生产者-消费者模式处理文件路径
- 添加批量处理机制减少锁竞争
- 异常处理增强:
- 添加try-catch块包装关键操作
- 增加文件访问错误处理
- 添加内存不足处理
- 功能扩展:
// 在FileInfo结构中添加更多文件属性
// 实现按不同比较策略配置(如比较完整内容)
// 添加进度显示功能
// 示例:增强的异常处理
try {
TraverseDirectories(rootDir);
} catch (const exception& e) {
wstringstream ss;
ss << L"遍历目录时发生错误: " << e.what();
Log(ss.str());
}
- 改进建议:
- 使用内存映射文件提升大文件读取性能
- 实现增量哈希计算优化内存使用
- 添加目录跳过列表功能
- 支持配置文件路径和参数
- 实现结果分页输出
使用时需要注意:
- 需要将目标目录替换为实际需要扫描的路径
- 需要处理Windows长路径问题(可添加
\\?\
前缀) - 需要管理员权限扫描系统目录
- 建议在x64模式下编译处理大文件