1 先实现一个简单程序
1.1 功能
- 用户可以输入一个源文件的路径和目标路径
- 点击开始,程序启动读取和合成
- 合成进度可见、合成步骤可见
1.2 思路
一个线程顺序读取文件,达到设定的缓存块就发给另一个合成线程,主线程用来进行数据传递、显示进度和一些文件信息。
1.3 设计ui
1.4 创建工作线程
work.h
在#ifndef WORK_H
#define WORK_H
#include <QObject>
#include <QRunnable>
#define DATA_SIZE 1024*1024
class ReadWork : public QObject,public QRunnable
{
Q_OBJECT
public:
explicit ReadWork(QObject *parent = nullptr);
void run() override;
void getFile(const QString &filePath);
signals:
void sendData(const QByteArray &data);
void sendFileInfo(const QString &fileInfo);
void msgTips(const QString &msg);
void updatePgBar(int num);
private:
QString filePath;
};
class WriteWork : public QObject,public QRunnable
{
Q_OBJECT
public:
explicit WriteWork(QObject *parent = nullptr);
void run() override;
void getPath(const QString &filePath);
public slots:
void getData(const QByteArray &data);
void getFileInfo(const QString &data);
signals:
void msgTips(const QString &msg);
void updatePgBar(int num);
private:
QString filePath;
QString fileInfo;
QList<QByteArray> arryList;
};
#endif // WORK_H
work.cpp
#include "work.h"
#include <QFile>
#include <QFileInfo>
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
ReadWork::ReadWork(QObject *parent) : QObject(parent),QRunnable()
{
filePath = "";
}
void ReadWork::run()
{
//判断文件是否存在
QFileInfo fileInfo(filePath);
if(!fileInfo.exists())
{
emit msgTips("文件不存在!");
return;
}
//获得文件信息
quint64 sourceFileSize = fileInfo.size();
QString fileInfoStr = QString("%1|%2|%3|%4")
.arg(fileInfo.fileName()).arg(sourceFileSize).arg(fileInfo.absoluteFilePath()).arg(fileInfo.suffix());
emit sendFileInfo(fileInfoStr);
emit msgTips(fileInfoStr);
//打开文件
QFile file(filePath);
if(!file.open(QIODevice::ReadOnly))
{
emit msgTips(QString("文件打开失败!%1。").arg(file.errorString()));
return;
}
//读取文件
QByteArray data;
quint64 fileSize = 0;
while(!file.atEnd())
{
int readSize = (sourceFileSize-file.pos()<DATA_SIZE)?(sourceFileSize-file.pos()):DATA_SIZE;
data = file.read(readSize);
fileSize += data.size();
int pgBarStep = static_cast<int>(fileSize*100/sourceFileSize);
emit updatePgBar(pgBarStep);
emit sendData(data);
//QThread::msleep(10);
}
file.close();
emit msgTips("读取完毕!");
}
void ReadWork::getFile(const QString &filePath)
{
this->filePath = filePath;
}
WriteWork::WriteWork(QObject *parent) : QObject(parent),QRunnable()
{
filePath = "";
fileInfo = "";
arryList.clear();
}
void WriteWork::run()
{
//如果文件路径为空,则使用当前应用程序所在路径
if(filePath.isEmpty())
filePath = QCoreApplication::applicationDirPath();
//创建文件
QFile file;
quint64 sourceFileSize = 0;
quint64 currentFileSize = 0;
QString fileName="";
//获取并解析文件信息
if(fileInfo.isEmpty())
{
emit msgTips("未收到文件信息");
return;
}
QStringList fileInfos = fileInfo.split("|");
fileName = fileInfos.at(0);
sourceFileSize = fileInfos.at(1).toInt();
//设置文件相关信息
file.setFileName(filePath+"/"+fileName);
emit msgTips("设置文件路径和名称完毕!");
//打开文件
if(!file.open(QIODevice::WriteOnly))
{
emit msgTips("文件打开失败:"+file.errorString());
return;
}
emit msgTips("文件打开成功!");
//写入数据
while(sourceFileSize-currentFileSize>0)
{
if(arryList.isEmpty())
continue;
QByteArray fileData = arryList.takeFirst();
quint64 size = file.write(fileData,fileData.size());
currentFileSize += size;
int step = static_cast<int>((currentFileSize*100)/sourceFileSize) ;
emit updatePgBar(step);
//QThread::msleep(20);
}
file.close();
emit msgTips("写入完毕!");
}
void WriteWork::getPath(const QString &filePath)
{
this->filePath = filePath;
}
void WriteWork::getData(const QByteArray &data)
{
arryList<<data;
}
void WriteWork::getFileInfo(const QString &data)
{
fileInfo = data;
}
1.5 主函数中调用
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "work.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void showMsgTips(const QString &str);
void showMsgTip(const QString &str);
void startWrite(const QString &str);
private slots:
void on_btnRead_clicked();
void on_btnWrite_clicked();
void on_btnStart_clicked();
private:
Ui::MainWindow *ui;
ReadWork *readThread;
WriteWork *writeThread;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QFileDialog>
#include <QThreadPool>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
readThread = new ReadWork;
writeThread = new WriteWork;
connect(readThread,SIGNAL(sendFileInfo(QString)),this,SLOT(startWrite(QString)));
connect(readThread,SIGNAL(sendData(QByteArray)),writeThread,SLOT(getData(QByteArray)));
connect(readThread,SIGNAL(msgTips(QString)),this,SLOT(showMsgTips(QString)));
connect(readThread,SIGNAL(updatePgBar(int)),ui->pgBarRead,SLOT(setValue(int)));
connect(writeThread,SIGNAL(msgTips(QString)),this,SLOT(showMsgTip(QString)));
connect(writeThread,SIGNAL(updatePgBar(int)),ui->pgBarWrite,SLOT(setValue(int)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::showMsgTips(const QString &str)
{
ui->txtEditRead->append(str);
}
void MainWindow::showMsgTip(const QString &str)
{
ui->txtEditWrite->append(str);
}
void MainWindow::startWrite(const QString &str)
{
writeThread->getPath(ui->txtWriteFileName->text());
writeThread->getFileInfo(str);
QThreadPool::globalInstance()->start(writeThread);
}
void MainWindow::on_btnRead_clicked()
{
QString readFilePath = QFileDialog::getOpenFileName(nullptr, "选择文件", "", "All Files (*.*)");
ui->txtReadFileName->setText(readFilePath);
}
void MainWindow::on_btnWrite_clicked()
{
QString writeFilePath = QFileDialog::getExistingDirectory(nullptr, "选择文件夹", "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
ui->txtWriteFileName->setText(writeFilePath);
}
void MainWindow::on_btnStart_clicked()
{
if(ui->txtReadFileName->text().isEmpty())
{
QMessageBox::information(this, tr("提示"), tr("文件不能为空"));
return;
}
readThread->getFile(ui->txtReadFileName->text());
QThreadPool::globalInstance()->start(readThread);
}
1.6 运行效果
1.7 遇到的问题
- 读取大文件的时候要分块读取,如果很大,直接使用readAll()函数把文件所有内容读入内存,会存在风险;
- 写线程一定是在读线程开始之后才能再开始
- 如果换块设置的很小,而文件又很大,这样在while函数中不断的触发发送数据信号,可能会导致程序来不及相应这个信号,导致读或者写错误。解决方法可以是增加延时函数或者把内存块设置大一点(让其在读写是比较耗时)
- 在最后一次读数据是,可能剩余的数据小于设置的缓存块大小,所以在这里要有个判断,小于缓存块大小,则使用剩余数据的大小;如果将每次读入数据的大小都设置成固定大小,那么可能会导致的你写的文件要大于实际文件的大小