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>

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林夕07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值