Qt高级——Qt数据可视化性能优化一、数据可视化简介1、数据可视化简介

数据可视化即采用图形图表等对采集的数据进行展示,可以非常直观的查看传感器采集到的数据。本文将使用Qt的标准组件QTableWidget、标准模型、自定义模型分别实现对数据的表格展示。

2、系统环境

个人PC:ThinkPad T450
操作系统:RHEL7.3 WorkStation
内存容量:8G
磁盘容量:SSD 100G
CPU:Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz

二、标准界面组件实现1、代码实现

MainWindow.h文件:

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QWidget>#include <QTableWidget>#include <QVBoxLayout>#include <QTableWidgetItem>#include <QList>#include <QString>struct Student{ char name[16]; char id[24]; char sex[8]; int age; char phone[16]; char hobby[24]; char company[16];};class MainWindow : public QWidget{ Q_OBJECTpublic: MainWindow(QWidget *parent = 0); ~MainWindow(); /** * @brief 生成数据 * @param size,生成数据的规模 */ void generateData(int size);private: /** * @brief 生成一行数据 * @param item,数据项 * @return 返回数据项链表 */ QList<QTableWidgetItem*> generateRow(const Student& item);private: QTableWidget* m_table;};#endif // MAINWINDOW_H

MainWindow.cpp文件:

#include "MainWindow.h"#include <QDebug>MainWindow::MainWindow(QWidget *parent) : QWidget(parent){ m_table = new QTableWidget(this); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_table); setLayout(layout); QStringList header; header << "Name" << "ID" << "Sex" << "Age" << "Phone" << "Hobby" << "Company"; m_table->setHorizontalHeaderLabels(header); m_table->setColumnCount(7);}MainWindow::~MainWindow(){}// 头部插入实现void MainWindow::generateData(int size){ Student* zhangsan = (Student*)malloc(sizeof(Student)); memset(zhangsan, 0, sizeof(Student)); strncpy(zhangsan->name, "zhangsan", strlen("zhangsan")); strncpy(zhangsan->id, "53302219861001xxxx", strlen("53302219861001xxxx")); strncpy(zhangsan->sex, "M", strlen("M")); zhangsan->age = 33; strncpy(zhangsan->phone, "18910108888", strlen("18910108888")); strncpy(zhangsan->hobby, "BasketBall, Play", strlen("BasketBall, Play")); strncpy(zhangsan->company, "Alibaba", strlen("Alibaba")); for(int i = 0; i < size; i++) { m_table->insertRow(0); QList<QTableWidgetItem*> items1 = generateRow(*zhangsan); for(int k = 0; k < items1.size(); k++) { m_table->setItem(0, k, items1.at(k)); } } delete zhangsan; qDebug() << sizeof(Student);}// 尾部插入实现void MainWindow::generateData(int size){ Student* zhangsan = (Student*)malloc(sizeof(Student)); memset(zhangsan, 0, sizeof(Student)); strncpy(zhangsan->name, "zhangsan", strlen("zhangsan")); strncpy(zhangsan->id, "53302219861001xxxx", strlen("53302219861001xxxx")); strncpy(zhangsan->sex, "M", strlen("M")); zhangsan->age = 33; strncpy(zhangsan->phone, "18910108888", strlen("18910108888")); strncpy(zhangsan->hobby, "BasketBall, Play", strlen("BasketBall, Play")); strncpy(zhangsan->company, "Alibaba", strlen("Alibaba")); for(int i = 0; i < size; i++) { m_table->insertRow(i); QList<QTableWidgetItem*> items1 = generateRow(*zhangsan); for(int k = 0; k < items1.size(); k++) { m_table->setItem(i, k, items1.at(k)); } } delete zhangsan; qDebug() << sizeof(Student);}QList<QTableWidgetItem*> MainWindow::generateRow(const Student &item){ QList<QTableWidgetItem*> ret; QTableWidgetItem* name = new QTableWidgetItem(); name->setData(Qt::DisplayRole, QString("%1").arg(item.name)); ret.append(name); QTableWidgetItem* id = new QTableWidgetItem(); id->setData(Qt::DisplayRole, QString("%1").arg(item.id)); ret.append(id); QTableWidgetItem* sex = new QTableWidgetItem(); sex->setData(Qt::DisplayRole, QString("%1").arg(item.sex)); ret.append(sex); QTableWidgetItem* age = new QTableWidgetItem(); age->setData(Qt::DisplayRole, QString("%1").arg(item.age)); ret.append(age); QTableWidgetItem* phone = new QTableWidgetItem(); phone->setData(Qt::DisplayRole, QString("%1").arg(item.phone)); ret.append(phone); QTableWidgetItem* hobby = new QTableWidgetItem(); hobby->setData(Qt::DisplayRole, QString("%1").arg(item.hobby)); ret.append(hobby); QTableWidgetItem* company = new QTableWidgetItem(); company->setData(Qt::DisplayRole, QString("%1").arg(item.company)); ret.append(company); return ret;}

main.cpp文件:

#include "MainWindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.generateData(500000); w.show(); return a.exec();}

2、性能分析
Student结构体如下:

struct Student{ char name[16]; char id[24]; char sex[8]; int age; char phone[16]; char hobby[24]; char company[16];};

Student结构体大小为108字节,根据生成的不同数量规模的数据,其程序占用的内存如下:

根据上述数据,在大规模数据量下,使用QTableWidget展示数据时,每条数据实际占用的内存是数据本身大小的15倍,数据量越大插入越耗时,头部插入耗时远远大于尾部追加插入。

三、标准模型实现1、代码实现

StudentTableModel.h文件:

#ifndef STUDENTTABLEMODEL_H#define STUDENTTABLEMODEL_H#include <QStandardItemModel>#include <QStandardItem>struct Student{ char name[16]; char id[24]; char sex[8]; int age; char phone[16]; char hobby[24]; char company[16];};class StudentTableModel : public QStandardItemModel{ Q_OBJECTpublic: StudentTableModel(); /** * @brief 生成数据 * @param size,数据规模 */ void generateData(int size); /** * @brief 生成一行数据 * @param item,数据对象 * @return 返回数据项链表 */ QList<QStandardItem*> generateRow(const Student& item); /** * @brief 追加一行 * @param item,数据对象 */ void appendRow(const Student& item);private: QStandardItem* m_root;//模型虚拟根节点};#endif // STUDENTTABLEMODEL_H

StudentTableModel.cpp文件:

#include "StudentTableModel.h"StudentTableModel::StudentTableModel(){ m_root = invisibleRootItem();}void StudentTableModel::generateData(int size){ Student* zhangsan = (Student*)malloc(sizeof(Student)); memset(zhangsan, 0, sizeof(Student)); strncpy(zhangsan->name, "zhangsan", strlen("zhangsan")); strncpy(zhangsan->id, "53302219861001xxxx", strlen("53302219861001xxxx")); strncpy(zhangsan->sex, "M", strlen("M")); zhangsan->age = 33; strncpy(zhangsan->phone, "18910108888", strlen("18910108888")); strncpy(zhangsan->hobby, "BasketBall, Play", strlen("BasketBall, Play")); strncpy(zhangsan->company, "Alibaba", strlen("Alibaba")); for(int i = 0; i < size; i++) { QList<QStandardItem*> items1 = generateRow(*zhangsan); // 尾部追加 m_root->appendRow(items1); // 头部插入 m_root->insertRow(0, items1); } delete zhangsan;}QList<QStandardItem*> StudentTableModel::generateRow(const Student &item){ QList<QStandardItem*> ret; QStandardItem* name = new QStandardItem(); name->setData(QString("%1").arg(item.name), Qt::DisplayRole); ret.append(name); QStandardItem* id = new QStandardItem(); id->setData(QString("%1").arg(item.id), Qt::DisplayRole); ret.append(id); QStandardItem* sex = new QStandardItem(); sex->setData(QString("%1").arg(item.sex), Qt::DisplayRole); ret.append(sex); QStandardItem* age = new QStandardItem(); age->setData(QString("%1").arg(item.age), Qt::DisplayRole); ret.append(age); QStandardItem* phone = new QStandardItem(); phone->setData(QString("%1").arg(item.phone), Qt::DisplayRole); ret.append(phone); QStandardItem* hobby = new QStandardItem(); hobby->setData(QString("%1").arg(item.hobby), Qt::DisplayRole); ret.append(hobby); QStandardItem* company = new QStandardItem(); company->setData(QString("%1").arg(item.company), Qt::DisplayRole); ret.append(company); return ret;}

MainWindow.h文件:

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QWidget>#include <QTableView>#include <QVBoxLayout>#include <QHeaderView>#include "StudentTableModel.h"class MainWindow : public QWidget{ Q_OBJECTpublic: MainWindow(QWidget *parent = 0); ~MainWindow(); void generateData(int size);private: QTableView* m_tableView; StudentTableModel* m_model;};#endif // MAINWINDOW_H

MainWindow.cpp文件:

#include "MainWindow.h"MainWindow::MainWindow(QWidget *parent): QWidget(parent){ m_tableView = new QTableView(this); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_tableView); setLayout(layout); m_model = new StudentTableModel(); m_tableView->setModel(m_model); QStringList header; header << "Name" << "ID" << "Sex" << "Age" << "Phone" << "Hobby" << "Company"; m_model->setHorizontalHeaderLabels(header);}MainWindow::~MainWindow(){}void MainWindow::generateData(int size){ m_model->generateData(size);}

main.cpp文件:

#include "MainWindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); w.generateData(500000); return a.exec();}2、性能分析

根据生成的不同数量规模的数据,其程序占用的内存如下:

使用QStandardItemModel与QTableView展示数据,每条数据实际占用内存的大小是数据本身大小的15倍,数据量越大插入越耗时,头部插入耗时远远大于尾部追加插入,其性能表现与QTableWidget相当。

四、自定义模型实现1、代码实现

StudentTableModel.h文件:

#ifndef STUDENTTABLEMODEL_H#define STUDENTTABLEMODEL_H#include <QAbstractTableModel>#include <QList>#include <QStringList>#include <QString>#include <QVariant>struct Student{ char name[16]; char id[24]; char sex[8]; int age; char phone[16]; char hobby[24]; char company[16];};class StudentTableModel : public QAbstractTableModel{ Q_OBJECTpublic: StudentTableModel(QObject* parent = NULL); virtual int rowCount(const QModelIndex &parent) const; virtual int columnCount(const QModelIndex &parent) const; virtual QVariant data(const QModelIndex &index, int role) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); virtual bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()); virtual bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()); void appendRow(const Student& item); void setHorizontalHeaderLabels(const QStringList& header);private: QStringList m_headers; QList<Student*> m_itemList;};#endif // STUDENTTABLEMODEL_H

StudentTableModel.cpp文件:

#include "StudentTableModel.h"StudentTableModel::StudentTableModel(QObject *parent): QAbstractTableModel(parent){}int StudentTableModel::rowCount(const QModelIndex &parent) const{ Q_UNUSED(parent); return m_itemList.size();}int StudentTableModel::columnCount(const QModelIndex &parent) const{ Q_UNUSED(parent); return m_headers.size();}QVariant StudentTableModel::data(const QModelIndex &index, int role) const{ if (!index.isValid()) return QVariant(); if (index.row() >= m_itemList.size() || index.row() < 0) return QVariant(); if (role == Qt::DisplayRole) { int row = index.row(); Student* data = m_itemList.at(row); int column = index.column(); switch(column) { case 0: return QString("%1").arg(data->name); case 1: return QString("%1").arg(data->id); case 2: return QString("%1").arg(data->sex); case 3: return data->age; case 4: return QString("%1").arg(data->phone); case 5: return QString("%1").arg(data->hobby); case 6: return QString("%1").arg(data->company); } } return QVariant();}QVariant StudentTableModel::headerData(int section, Qt::Orientation orientation, int role) const{ if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { return m_headers.at(section); } return QVariant();}bool StudentTableModel::insertRows(int position, int rows, const QModelIndex &index){ Q_UNUSED(index); beginInsertRows(QModelIndex(), position, position + rows - 1); for (int row = 0; row < rows; ++row) {// m_itemList.insert(position, ); } endInsertRows(); return true;}bool StudentTableModel::removeRows(int position, int rows, const QModelIndex &index){ Q_UNUSED(index); beginRemoveRows(QModelIndex(), position, position + rows - 1); for (int row = 0; row < rows; ++row) m_itemList.removeAt(position); endRemoveRows(); return true;}void StudentTableModel::appendRow(const Student &item){ Student* data = (Student*)malloc(sizeof(Student)); memset(data, 0, sizeof(Student)); strncpy(data->name, item.name, strlen(item.name)); strncpy(data->id, item.id, strlen(item.id)); strncpy(data->sex, item.sex, strlen(item.sex)); data->age = item.age; strncpy(data->phone, item.phone, strlen(item.phone)); strncpy(data->hobby, item.hobby, strlen(item.hobby)); strncpy(data->company, item.company, strlen(item.company)); int row = m_itemList.size(); insertRows(0, 1); m_itemList.append(data);// m_itemList.insert(0, data);}void StudentTableModel::setHorizontalHeaderLabels(const QStringList &header){ m_headers = header;}bool StudentTableModel::setData(const QModelIndex &index, const QVariant &value, int role){ if (index.isValid() && role == Qt::EditRole) { int row = index.row(); // mpdify data emit(dataChanged(index, index)); return true; } return false;}Qt::ItemFlags StudentTableModel::flags(const QModelIndex &index) const{ if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;}

MainWindow.h文件:

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QWidget>#include <QTableView>#include <QVBoxLayout>#include "StudentTableModel.h"class MainWindow : public QWidget{ Q_OBJECTpublic: MainWindow(QWidget *parent = 0); ~MainWindow(); void generateData(int size);private: StudentTableModel* m_model; QTableView* m_tableView;};#endif // MAINWINDOW_H

MainWindow.cpp文件:

#include "MainWindow.h"MainWindow::MainWindow(QWidget *parent): QWidget(parent){ m_tableView = new QTableView(this); m_model = new StudentTableModel(); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_tableView); setLayout(layout); QStringList header; header << "Name" << "ID" << "Sex" << "Age" << "Phone" << "Hobby" << "Company"; m_model->setHorizontalHeaderLabels(header); m_tableView->setUpdatesEnabled(true); m_tableView->setModel(m_model);}MainWindow::~MainWindow(){}void MainWindow::generateData(int size){ Student* zhangsan = (Student*)malloc(sizeof(Student)); memset(zhangsan, 0, sizeof(Student)); strncpy(zhangsan->name, "zhangsan", strlen("zhangsan")); strncpy(zhangsan->id, "53302219861001xxxx", strlen("53302219861001xxxx")); strncpy(zhangsan->sex, "M", strlen("M")); zhangsan->age = 33; strncpy(zhangsan->phone, "18910108888", strlen("18910108888")); strncpy(zhangsan->hobby, "BasketBall, Play", strlen("BasketBall, Play")); strncpy(zhangsan->company, "Alibaba", strlen("Alibaba")); for(int i = 0; i < size; i++) { m_model->appendRow(*zhangsan); } delete zhangsan;}

main.cpp文件:

#include "MainWindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); w.generateData(1000000); return a.exec();}

2、性能分析
根据生成的不同数量规模的数据,其程序占用的内存如下:

使用QAbstractTableModel派生类与QTableView展示数据,每条数据实际占用内存的大小是数据本身大小的1.5倍,数据量越大插入越耗时,由于底层数据结构采用链表实现,头部插入耗时与尾部追加插入耗时相当,但内存空间占用大幅下降。
将底层数据结构换成QVector,根据生成的不同数量规模的数据,其程序占用的内存如下:

使用QVector作为模型的底层数据结构存储数据,其内存占用与QList相当,尾部追加插入耗时与QList相当,但头部插入比QList耗时较多。