Qt实现一个重复文件检测小工具(原理:通过md5校验)

简介: Qt实现一个重复文件检测小工具(原理:通过md5校验)

介绍

先看成品图


设计原理


通过递归遍历文件夹获取到所有文件,然后将所有的文件通过线程的形式进行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 &quot;华文行楷&quot;;</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>
目录
相关文章
|
6月前
|
计算机视觉 数据格式
使用opencv在Qt控件上播放mp4文件
使用opencv在Qt控件上播放mp4文件
208 2
|
6月前
|
存储 安全 编译器
【Qt 底层机制之信号和槽 】深入探究Qt信号和槽背后的原理
【Qt 底层机制之信号和槽 】深入探究Qt信号和槽背后的原理
1883 4
|
6月前
|
存储 C++
基于Qt的简易文件压缩与解压缩工具设计与实现
基于Qt的简易文件压缩与解压缩工具设计与实现
300 1
|
6月前
【QT】创建、打开QT项目只显示.pro文件的问题解决
【QT】创建、打开QT项目只显示.pro文件的问题解决
638 0
|
6月前
|
传感器 算法 数据格式
QT Modbus RTU调试助手(包含算法实现CRC MODBUS16校验)
QT Modbus RTU调试助手(包含算法实现CRC MODBUS16校验)
329 0
|
4月前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
173 1
Qt(C++)开发一款图片防盗用水印制作小工具
|
3月前
【qt】如何读取文件并拆分信息?
【qt】如何读取文件并拆分信息?
31 0
|
4月前
|
C++
基于QT实现的拷贝文件以及实时进度条(简易版)
1.基于按钮或者菜单栏的槽里去写逻辑函数(我这边用的是菜单栏),ui实现的进度条 2.创建两个对象,一个是源文件,一个是目标文件分别用getopenfileName、getsavefileName函数即可。 3.利用QFile类去实现对两个文件的创建,因为QFile中可以获取文件的属性已经读写等。 4.循环的去读取源文件中的数据,然后写入目标文件
455 6
|
2月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
3月前
|
存储 算法 C++
【C++】C++ QT实现Huffman编码器与解码器(源码+课程论文+文件)【独一无二】
【C++】C++ QT实现Huffman编码器与解码器(源码+课程论文+文件)【独一无二】