天天看點

Qt進階——Qt資料可視化性能優化

一、資料可視化簡介

1、資料可視化簡介

資料可視化即采用圖形圖表等對采集的資料進行展示,可以非常直覺的檢視傳感器采集到的資料。本文将使用Qt的标準元件QTableWidget、标準模型、自定義模型分别實作對資料的表格展示。

2、系統環境

個人PC:ThinkPad T450

作業系統:RHEL7.3 WorkStation

記憶體容量:8G

磁盤容量:SSD 100G

CPU:Intel® Core™ 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_OBJECT
public:
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_OBJECT
public:
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_OBJECT
public:
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_OBJECT
public:
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_OBJECT
public:
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();
}           

點選領取Qt學習資料+視訊教程~「連結」

2、性能分析

根據生成的不同數量規模的資料,其程式占用的記憶體如下:

使用QAbstractTableModel派生類與QTableView展示資料,每條資料實際占用記憶體的大小是資料本身大小的1.5倍,資料量越大插入越耗時,由于底層資料結構采用連結清單實作,頭部插入耗時與尾部追加插入耗時相當,但記憶體空間占用大幅下降。

将底層資料結構換成QVector,根據生成的不同數量規模的資料,其程式占用的記憶體如下:

使用QVector作為模型的底層資料結構存儲資料,其記憶體占用與QList相當,尾部追加插入耗時與QList相當,但頭部插入比QList耗時較多。

繼續閱讀