Visual Studio 2022开发C++程序实现目录下重复文件查找

发布于:2025-02-28 ⋅ 阅读:(140) ⋅ 点赞:(0)

在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;
}

代码说明和增强改进建议:

  1. 多线程架构
  • 使用独立的线程进行目录遍历
  • 使用async/async进行并行内容比较和哈希计算
  • 使用互斥锁保护共享数据结构
  1. 性能优化
  • 增加线程池管理(可使用线程池库)
  • 实现生产者-消费者模式处理文件路径
  • 添加批量处理机制减少锁竞争
  1. 异常处理增强
  • 添加try-catch块包装关键操作
  • 增加文件访问错误处理
  • 添加内存不足处理
  1. 功能扩展
// 在FileInfo结构中添加更多文件属性
// 实现按不同比较策略配置(如比较完整内容)
// 添加进度显示功能

// 示例:增强的异常处理
try {
    TraverseDirectories(rootDir);
} catch (const exception& e) {
    wstringstream ss;
    ss << L"遍历目录时发生错误: " << e.what();
    Log(ss.str());
}
  1. 改进建议
  • 使用内存映射文件提升大文件读取性能
  • 实现增量哈希计算优化内存使用
  • 添加目录跳过列表功能
  • 支持配置文件路径和参数
  • 实现结果分页输出

使用时需要注意:

  1. 需要将目标目录替换为实际需要扫描的路径
  2. 需要处理Windows长路径问题(可添加\\?\前缀)
  3. 需要管理员权限扫描系统目录
  4. 建议在x64模式下编译处理大文件

网站公告

今日签到

点亮在社区的每一天
去签到