当前位置:网站首页>Qt实现一个重复文件检测小工具(原理:通过md5校验)
Qt实现一个重复文件检测小工具(原理:通过md5校验)
2022-07-30 05:39:00 【林夕07】
介绍
先看成品图

设计原理
通过递归遍历文件夹获取到所有文件,然后将所有的文件通过线程的形式进行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>
边栏推荐
猜你喜欢
随机推荐
【Koltin Flow(一)】五种创建flow的方式
SOA(面向服务架构)是什么?
Anaconda安装教程
What is SOA (Service Oriented Architecture)?
字符串(一) 哈希
进程间的通信方式简介
net start mysql MySQL service is starting. MySQL service failed to start.The service did not report any errors.
排列数字(DAY90)dfs
839. Simulated heap
torch.optim.Adam()
[GLib] What is GType
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000
相对路径和绝对路径的区别
Machine Learning - Gradient Descent Optimization - C language implementation
[Mysql] CONVERT function
C语言必会15个文件函数
Teach you to completely uninstall MySQL
navicat连接MySQL报错:1045 - Access denied for user ‘root‘@‘localhost‘ (using password YES)
MySQL Soul 16 Questions, how many questions can you last?
My first understanding of MySql, and the basic syntax of DDL and DML and DQL in sql statements






![[详解C语言]一文带你玩转数组](/img/1b/245fdc7f3711cf794175da7a93b128.png)


