C++ 如何从Qt中的大文件异步加载数据?
我正在使用Qt5.2.1实现一个程序,该程序从文件中读取数据(可能是几个字节到几个GB),并以依赖于每个字节的方式可视化数据。我这里的示例是一个十六进制查看器 一个对象进行读取,并在读取新数据块时发出信号C++ 如何从Qt中的大文件异步加载数据?,c++,multithreading,qt,io,C++,Multithreading,Qt,Io,我正在使用Qt5.2.1实现一个程序,该程序从文件中读取数据(可能是几个字节到几个GB),并以依赖于每个字节的方式可视化数据。我这里的示例是一个十六进制查看器 一个对象进行读取,并在读取新数据块时发出信号dataRead()。该信号携带一个指向QByteArray的指针,如下所示: filereader.cpp 第一个问题是,如果只是按原样运行,GUI线程将完全没有响应。所有dataRead()信号将在重新绘制GUI之前发出 (可以运行,当您使用大于1kB的文件时,您将看到这种行为。) 根据对我
dataRead()
。该信号携带一个指向QByteArray
的指针,如下所示:
filereader.cpp
第一个问题是,如果只是按原样运行,GUI线程将完全没有响应。所有dataRead()
信号将在重新绘制GUI之前发出
(可以运行,当您使用大于1kB的文件时,您将看到这种行为。)
根据对我论坛帖子的回复和对另一个堆栈溢出问题的回答,答案是:使用线程。但这两个答案都没有详细说明如何调整数据本身,也没有详细说明如何避免常见错误和陷阱
如果数据很小(大约一百个字节),我就用信号发出它。但是,如果文件大小为GB(edit),或者文件位于基于网络的文件系统上,例如NFS、Samba share,我不希望UI仅仅因为读取文件阻塞而锁定
第二个问题是,在发射器中使用new
和在接收器中使用delete
的机制似乎有点幼稚:我有效地将整个堆用作跨线程队列
问题1:Qt是否有更好的/惯用的方法在线程间移动数据,同时限制内存消耗?它是否有线程安全队列或其他结构可以简化整个过程
问题2:我必须自己实现线程等吗?我不太喜欢重新发明轮子,特别是在内存管理和线程方面。是否有更高级别的结构可以做到这一点,就像网络传输的结构一样?首先,您的应用程序中根本没有任何多线程。您的
FileReader
类是QThread
的子类,但这并不意味着所有FileReader
方法都将在另一个线程中执行。事实上,所有操作都是在主(GUI)线程中执行的
FileReader
应该是QObject
而不是QThread
子类。然后创建一个基本的QThread
对象,并使用QObject::moveToThread
将工作线程(读卡器)移动到该对象。您可以阅读有关此技术的信息
确保已使用qRegisterMetaType
注册了FileReader::State
类型。这是Qt信号插槽连接跨不同线程工作所必需的
例如:
HexViewer::HexViewer(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::HexViewer),
_fileReader(new FileReader())
{
qRegisterMetaType<FileReader::State>("FileReader::State");
QThread *readerThread = new QThread(this);
readerThread->setObjectName("ReaderThread");
connect(readerThread, SIGNAL(finished()),
_fileReader, SLOT(deleteLater()));
_fileReader->moveToThread(readerThread);
readerThread->start();
_ui->setupUi(this);
...
}
void HexViewer::on_quitButton_clicked()
{
_fileReader->thread()->quit();
_fileReader->thread()->wait();
qApp->quit();
}
但无论如何,主要问题不在于多线程。问题在于,QTextEdit::insertPlainText
操作并不便宜,尤其是当您拥有大量数据时FileReader
快速读取文件数据,然后用新的数据部分淹没小部件以显示
必须注意的是,HexViewer::loadData
的实现非常无效。您逐个字符插入文本数据,这使得QTextEdit
不断地重新绘制其内容并冻结GUI
您应该首先准备生成的十六进制字符串(请注意,数据参数不再是指针):
void HexViewer::loadData(QByteArray数据)
{
QString tmp=data.toHex();
QString六进制字符串;
hexString.reserve(tmp.size()*1.5);
常数int hexLen=2;
对于(int i=0;ihexTextView->insertPlainText(hextstring);
}
无论如何,应用程序的瓶颈不是文件读取,而是更新。按块加载数据,然后使用QTextEdit::insertPlainText
将其附加到小部件中不会加快任何速度。对于小于1Mb的文件,一次读取整个文件,然后在一个步骤中将结果文本设置为小部件会更快
我想,使用默认的Qt小部件,您无法轻松显示大于数兆字节的大型文本。此任务需要一些非平凡的方法,这些方法通常与多线程或异步数据加载无关。这一切都是关于创建一些棘手的小部件,它不会试图立即显示其巨大的内容
QTextEdit
。这个ui->hexTextView->insertPlainText
将在您读取文件的1/10之前占用整个内存。在我看来,您应该使用QTableView
来显示和编辑数据。为此,您应该继承QAbstractTableModel
。在一行中,您应该显示16个字节。前16列为十六进制格式,下一列为ASCII格式。这不应该太复杂。只需阅读QAbstractTableModel
的可怕文档。缓存数据在这里是最重要的。如果我有时间,我将给出代码示例#包括
#包括
#包括
类LargeFileCache:公共QObject
{
Q_对象
公众:
显式大文件缓存(QObject*parent=0);
字符字节(qint64位置);
qint64 FileSize()常量;
信号:
公众时段:
void SetFileName(常量QString&filename);
私人:
静态常量int kPageSize;
结构页{
秦T64偏移量;
QByteArray数据;
};
私人:
int-maxPageCount;
qint64文件大小;
QFile文件;
队列页面;
};
#包括
类大文件缓存;
类LageFileDataModel:publ
void HexViewer::loadData(QByteArray *data)
{
QString hexString = data->toHex();
for (int i = 0; i < hexString.length(); i+=2)
{
_ui->hexTextView->insertPlainText(hexString.at(i));
_ui->hexTextView->insertPlainText(hexString.at(i+1));
_ui->hexTextView->insertPlainText(" ");
}
delete data;
}
HexViewer::HexViewer(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::HexViewer),
_fileReader(new FileReader())
{
qRegisterMetaType<FileReader::State>("FileReader::State");
QThread *readerThread = new QThread(this);
readerThread->setObjectName("ReaderThread");
connect(readerThread, SIGNAL(finished()),
_fileReader, SLOT(deleteLater()));
_fileReader->moveToThread(readerThread);
readerThread->start();
_ui->setupUi(this);
...
}
void HexViewer::on_quitButton_clicked()
{
_fileReader->thread()->quit();
_fileReader->thread()->wait();
qApp->quit();
}
while(!inFile.atEnd())
{
QByteArray *qa = new QByteArray(inFile.read(DATA_SIZE));
qDebug() << "emitting dataRead()";
emit dataRead(qa);
}
while(!inFile.atEnd())
{
QByteArray qa = inFile.read(DATA_SIZE);
qDebug() << "emitting dataRead()";
emit dataRead(qa);
}
void HexViewer::loadData(QByteArray data)
{
QString tmp = data.toHex();
QString hexString;
hexString.reserve(tmp.size() * 1.5);
const int hexLen = 2;
for (int i = 0; i < tmp.size(); i += hexLen)
{
hexString.append(tmp.mid(i, hexLen) + " ");
}
_ui->hexTextView->insertPlainText(hexString);
}
#include <QObject>
#include <QFile>
#include <QQueue>
class LargeFileCache : public QObject
{
Q_OBJECT
public:
explicit LargeFileCache(QObject *parent = 0);
char geByte(qint64 pos);
qint64 FileSize() const;
signals:
public slots:
void SetFileName(const QString& filename);
private:
static const int kPageSize;
struct Page {
qint64 offset;
QByteArray data;
};
private:
int maxPageCount;
qint64 fileSize;
QFile file;
QQueue<Page> pages;
};
#include <QAbstractTableModel>
class LargeFileCache;
class LageFileDataModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit LageFileDataModel(QObject *parent);
// QAbstractTableModel
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
signals:
public slots:
void setFileName(const QString &fileName);
private:
LargeFileCache *cachedData;
};
#include "lagefiledatamodel.h"
#include "largefilecache.h"
static const int kBytesPerRow = 16;
LageFileDataModel::LageFileDataModel(QObject *parent)
: QAbstractTableModel(parent)
{
cachedData = new LargeFileCache(this);
}
int LageFileDataModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return (cachedData->FileSize() + kBytesPerRow - 1)/kBytesPerRow;
}
int LageFileDataModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return kBytesPerRow;
}
QVariant LageFileDataModel::data(const QModelIndex &index, int role) const
{
if (index.parent().isValid())
return QVariant();
if (index.isValid()) {
if (role == Qt::DisplayRole) {
qint64 pos = index.row()*kBytesPerRow + index.column();
if (pos>=cachedData->FileSize())
return QString();
return QString::number((unsigned char)cachedData->geByte(pos), 0x10);
}
}
return QVariant();
}
void LageFileDataModel::setFileName(const QString &fileName)
{
beginResetModel();
cachedData->SetFileName(fileName);
endResetModel();
}
#include "largefilecache.h"
const int LargeFileCache::kPageSize = 1024*4;
LargeFileCache::LargeFileCache(QObject *parent)
: QObject(parent)
, maxPageCount(1024)
{
}
char LargeFileCache::geByte(qint64 pos)
{
// largefilecache
if (pos>=fileSize)
return 0;
for (int i=0, n=pages.size(); i<n; ++i) {
int k = pos - pages.at(i).offset;
if (k>=0 && k< pages.at(i).data.size()) {
pages.enqueue(pages.takeAt(i));
return pages.back().data.at(k);
}
}
Page newPage;
newPage.offset = (pos/kPageSize)*kPageSize;
file.seek(newPage.offset);
newPage.data = file.read(kPageSize);
pages.push_front(newPage);
while (pages.count()>maxPageCount)
pages.dequeue();
return newPage.data.at(pos - newPage.offset);
}
qint64 LargeFileCache::FileSize() const
{
return fileSize;
}
void LargeFileCache::SetFileName(const QString &filename)
{
file.close();
file.setFileName(filename);
file.open(QFile::ReadOnly);
fileSize = file.size();
}