介绍
先看成品图
设计原理
通过递归遍历文件夹获取到所有文件,然后将所有的文件通过线程的形式进行md5(MD5信息摘要算法)计算得到一个32位的十六进制序列,以这个序列为key,对应的文件名为value(此处可能有多个),所以这个map设计为QHash<QByteArray, QStringList>。下面的进度条是在每进行一个md5计算就发出一个信号,然后更新进度条。
整体框架
MainWindows类用于ui显示和一些逻辑代码。
FileMd5类用于计算MD5数值。
信号与槽
connect(ui->GetFiles, QOverload<bool>::of(&QPushButton::clicked), this, QOverload<bool>::of(&MainWindow::onGetFiles)); connect(&md5, QOverload<const QHash<QByteArray, QStringList>&>::of(&FileMd5::GotFilesMd5), this, QOverload<const QHash<QByteArray, QStringList>&>::of(&MainWindow::onGotFilesMd5)); connect(this, QOverload<const QString&>::of(&MainWindow::GotFilesMd5), &md5, QOverload<const QString&>::of(&FileMd5::onGetFileMd5)); connect(&md5, QOverload<int, int>::of(&FileMd5::NowProgress), this, QOverload<int, int>::of(&MainWindow::onNowProgress)); connect(ui->listWidgetMd5, QOverload<const QString &>::of(&QListWidget::currentTextChanged), this, QOverload<const QString &>::of(&MainWindow::onCurrentTextChanged));
填充QHash<QByteArray, QStringList>
void FileMd5::onGetFileMd5(const QString &path) { QHash<QByteArray, QStringList> ret; QStringList files = GetFiles(path); for (int i = 0; i < files.size(); ++i) { QByteArray md5 = GetFileMd5(files.at(i)).toHex(); // 计算md5值 qDebug() << files.at(i) << "\t" << md5; ret[md5].append(files.at(i)); emit NowProgress(i + 1, files.size()); // 发送当前进度 } emit GotFilesMd5(ret); }
计算单个文件的Md5
QByteArray FileMd5::GetFileMd5(const QString &fileName) { QFile file(fileName, this); const bool isOpen = file.open(QIODevice::ReadOnly); if( true == isOpen)//以只读形式打开文件 { QCryptographicHash hash(QCryptographicHash::Md5); while(false == file.atEnd()) { QByteArray data = file.read(100 * 1024 * 1024);// 100m 实际内容若不足只读实际大小 //QByteArray catalog = file.readAll(); // 小文件可以一直全读在内存中,大文件必须分批处理 hash.addData(data); qApp->processEvents();//执行事件循环 防止界面卡顿。 } QByteArray md5 = hash.result(); file.close();//及时关闭 return md5; } return QByteArray(); }
递归遍历获得所有所有文件
QStringList FileMd5::GetFiles(const QString &path) { QStringList ret; QDir dir(path); QFileInfoList infoList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);//返回文件信息 不要当前目录和上一级目录 for (int i = 0; i < infoList.count(); ++i) { QFileInfo info = infoList.at(i); if(true == info.isDir()) // 是目录就继续递归 { QStringList files = GetFiles(info.absoluteFilePath()); ret.append(files); } else // 文件就直接给追加文件名 { //qDebug() << info.fileName(); //qDebug() << info.absoluteFilePath(); ret.append(info.absoluteFilePath()); //qDebug() << info.absoluteFilePath(); } } return ret; }
设置进度条数值
void MainWindow::onNowProgress(int curr, int total) { // 方法一 //ui->progressBar->setValue(static_cast<double>(curr) / total * 100); // 方法二 ui->progressBar->setValue(curr); ui->progressBar->setMaximum(total); ui->progressBar->setMinimum(0); }
获取当前Md5值下重复的文件
void MainWindow::onCurrentTextChanged(const QString &text) { ui->listWidgetRepetition->clear(); //qDebug() << text; QByteArray temp = text.toUtf8(); qDebug() << temp; QStringList files = this->md5Map[text.toLocal8Bit()]; ui->listWidgetRepetition->addItems(files); }
完整代码
FileMd5.h
#ifndef FILEMD5_H #define FILEMD5_H #include <QObject> #include <QStringList> #include <QHash> // 无序 快 #include <QMap> // 有序 慢 class FileMd5 : public QObject { Q_OBJECT public: FileMd5(QObject* parent = nullptr); signals: void GotFilesMd5(const QHash<QByteArray, QStringList>& md5); // 将进度传出去 void NowProgress(int curr, int total); public slots: void onGetFileMd5(const QString& path); private: QStringList GetFiles(const QString& path); QByteArray GetFileMd5(const QString& fileName); }; #endif // FILEMD5_H
FileMd5.cpp
#include "FileMd5.h" #include <QFile> #include <QMessageBox> #include <QDebug> #include <QCryptographicHash> #include <QApplication> #include <QDir> // 目录类 #include <QFileInfo> // 文件信息类 FileMd5::FileMd5(QObject *parent) { } void FileMd5::onGetFileMd5(const QString &path) { QHash<QByteArray, QStringList> ret; QStringList files = GetFiles(path); for (int i = 0; i < files.size(); ++i) { QByteArray md5 = GetFileMd5(files.at(i)).toHex(); qDebug() << files.at(i) << "\t" << md5; ret[md5].append(files.at(i)); emit NowProgress(i + 1, files.size()); } emit GotFilesMd5(ret); } QStringList FileMd5::GetFiles(const QString &path) { QStringList ret; QDir dir(path); QFileInfoList infoList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);//返回文件信息 不要当前目录和上一级目录 for (int i = 0; i < infoList.count(); ++i) { QFileInfo info = infoList.at(i); if(true == info.isDir()) // 是目录就继续递归 { QStringList files = GetFiles(info.absoluteFilePath()); ret.append(files); } else // 文件就直接给追加文件名 { //qDebug() << info.fileName(); //qDebug() << info.absoluteFilePath(); ret.append(info.absoluteFilePath()); //qDebug() << info.absoluteFilePath(); } } return ret; } QByteArray FileMd5::GetFileMd5(const QString &fileName) { QFile file(fileName, this); const bool isOpen = file.open(QIODevice::ReadOnly); if( true == isOpen)//以只读形式打开文件 { QCryptographicHash hash(QCryptographicHash::Md5); while(false == file.atEnd()) { QByteArray data = file.read(100 * 1024 * 1024);// 100m 实际内容若不足只读实际大小 //QByteArray catalog = file.readAll(); // 小文件可以一直全读在内存中,大文件必须分批处理 hash.addData(data); qApp->processEvents();//执行事件循环 防止界面卡顿。 } QByteArray md5 = hash.result(); file.close();//及时关闭 return md5; } return QByteArray(); }
MainWindows.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QThread> #include "FileMd5.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); QStringList GetFiles(const QString& path); QByteArray GetFileMd5(const QString& fileName); signals: void GotFilesMd5(const QString& path); private slots: void onGetFiles(bool checked = false); void onGotFilesMd5(const QHash<QByteArray, QStringList>& md5); void onNowProgress(int curr, int total); void onCurrentTextChanged(const QString& text); private: Ui::MainWindow *ui; // md5, (file1, file2) 相同的文件放在QstringList中 FileMd5 md5; QThread thread; QHash<QByteArray, QStringList> md5Map; }; #endif // MAINWINDOW_H
MainWindows.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QCryptographicHash> #include <QDebug> #include <QFile> #include <QFileDialog> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); this->setWindowTitle("重复文件检测"); thread.start(); md5.moveToThread(thread.thread()); // thread() 返回 QThead* bool ret = connect(ui->GetFiles, QOverload<bool>::of(&QPushButton::clicked), this, QOverload<bool>::of(&MainWindow::onGetFiles)); qDebug() << ret; // 如果编译出现 Make sure 'QHash<QByteArray, QStringList>' is registered using qRegisterMetatype 就是需要注册这个类型。 qRegisterMetaType<QHash<QByteArray, QStringList>>("QHash<QByteArray, QStringList>"); ret = connect(&md5, QOverload<const QHash<QByteArray, QStringList>&>::of(&FileMd5::GotFilesMd5), this, QOverload<const QHash<QByteArray, QStringList>&>::of(&MainWindow::onGotFilesMd5)); qDebug() << ret; ret = connect(this, QOverload<const QString&>::of(&MainWindow::GotFilesMd5), &md5, QOverload<const QString&>::of(&FileMd5::onGetFileMd5)); qDebug() << ret; ret = connect(&md5, QOverload<int, int>::of(&FileMd5::NowProgress), this, QOverload<int, int>::of(&MainWindow::onNowProgress)); qDebug() << ret; ret = connect(ui->listWidgetMd5, QOverload<const QString &>::of(&QListWidget::currentTextChanged), this, QOverload<const QString &>::of(&MainWindow::onCurrentTextChanged)); qDebug() << ret; // ui->OpenFile->hide();//设置隐藏 ui->lineEdit->setReadOnly(true);//设置只读 } MainWindow::~MainWindow() { thread.exit(); thread.wait(10000); delete ui; } void MainWindow::onGetFiles(bool checked) { QString path = QFileDialog::getExistingDirectory(this, "选择文件夹", ".", QFileDialog::ShowDirsOnly);//仅显示目录 且默认当前路径 ui->lineEdit->setText(path); ui->progressBar->setValue(0); emit GotFilesMd5(path); } void MainWindow::onGotFilesMd5(const QHash<QByteArray, QStringList> &md5) { ui->listWidgetMd5->clear(); ui->listWidgetRepetition->clear(); this->md5Map = static_cast<QHash<QByteArray, QStringList>>(md5); qDebug() << "md5Map" << &md5Map; for(QHash<QByteArray, QStringList>::ConstIterator iter = md5.constBegin(); iter != md5.constEnd(); ++iter) { qDebug() << "md5:" << iter.key() << "\t" << "count:" << iter.value().count(); if(iter.value().count() > 1) { qDebug() << "file:" << iter.value(); } ui->listWidgetMd5->addItem(iter.key()); } } void MainWindow::onNowProgress(int curr, int total) { // 方法一 //ui->progressBar->setValue(static_cast<double>(curr) / total * 100); // 方法二 ui->progressBar->setValue(curr); ui->progressBar->setMaximum(total); ui->progressBar->setMinimum(0); } void MainWindow::onCurrentTextChanged(const QString &text) { ui->listWidgetRepetition->clear(); //qDebug() << text; QByteArray temp = text.toUtf8(); qDebug() << temp; QStringList files = this->md5Map[text.toLocal8Bit()]; ui->listWidgetRepetition->addItems(files); }
MainWindows.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QLabel" name="title"> <property name="minimumSize"> <size> <width>0</width> <height>50</height> </size> </property> <property name="styleSheet"> <string notr="true">font: 24pt "华文行楷";</string> </property> <property name="text"> <string>重复文件检测工具</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item row="1" column="0"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QLineEdit" name="lineEdit"> <property name="minimumSize"> <size> <width>0</width> <height>35</height> </size> </property> </widget> </item> <item> <widget class="QPushButton" name="GetFiles"> <property name="minimumSize"> <size> <width>0</width> <height>35</height> </size> </property> <property name="text"> <string>选择文件夹</string> </property> </widget> </item> </layout> </item> <item row="2" column="0"> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> <widget class="QListWidget" name="listWidgetMd5"/> </item> <item> <widget class="QListWidget" name="listWidgetRepetition"/> </item> </layout> </item> <item row="3" column="0"> <widget class="QProgressBar" name="progressBar"> <property name="value"> <number>0</number> </property> </widget> </item> </layout> </widget> </widget> <resources/> <connections/> </ui>