C++ 如何从Qt中的大文件异步加载数据?

C++ 如何从Qt中的大文件异步加载数据?,c++,multithreading,qt,io,C++,Multithreading,Qt,Io,我正在使用Qt5.2.1实现一个程序,该程序从文件中读取数据(可能是几个字节到几个GB),并以依赖于每个字节的方式可视化数据。我这里的示例是一个十六进制查看器 一个对象进行读取,并在读取新数据块时发出信号dataRead()。该信号携带一个指向QByteArray的指针,如下所示: filereader.cpp 第一个问题是,如果只是按原样运行,GUI线程将完全没有响应。所有dataRead()信号将在重新绘制GUI之前发出 (可以运行,当您使用大于1kB的文件时,您将看到这种行为。) 根据对我

我正在使用Qt5.2.1实现一个程序,该程序从文件中读取数据(可能是几个字节到几个GB),并以依赖于每个字节的方式可视化数据。我这里的示例是一个十六进制查看器

一个对象进行读取,并在读取新数据块时发出信号
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小部件,您无法轻松显示大于数兆字节的大型文本。此任务需要一些非平凡的方法,这些方法通常与多线程或异步数据加载无关。这一切都是关于创建一些棘手的小部件,它不会试图立即显示其巨大的内容

  • 如果您计划编辑10GB文件,请忘记
    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();
    }