序列化是信息傳輸和持久化的基石,用於網絡傳輸的序列化,主流是JSON和XML,而持久化保存,則一般是二進制文件,在Qt中,提供了QDataStream類為我們的程序提供了讀寫二進制數據的能力。
QDataStream類實現了序列化C++的基本數據類型的功能,比如char,short,int,char* 等等,不但如此還可以直接序列化 QMap ,QList之類的容器(需要保證容器內的元素是基本類型元素)。但是往往程序中包含了復雜的數據結構,此時就不能直接進行序列化了。因此我們需要將復雜數據類型分解成獨立的基本數據類型分別進行序列化。
typedef struct ProjectInfo{ QString projectName; QString imgPath; QString annotationMeta; QString createTime; int currentImgIndex; QMap<QString,QList<RectMeta>> markCollection; } Project; Q_DECLARE_METATYPE(ProjectInfo); typedef struct RectMetaInfo{ QString text; qreal x; qreal y; qreal w; qreal h; } RectMeta; Q_DECLARE_METATYPE(RectMetaInfo);
上述代碼包含了兩個結構體,
其中一個結構體RectMetaInfo中,全部都是基本類型,
而另外一個結構體ProjectInfo,則包含了基本類型和復雜的容器類型
此時我如果直接序列化結構體ProjectInfo,顯然是行不通的,因此我們需要逐步分解序列化。
首先結構體ProjectInfo包含了結構體RectMetaInfo,那么先序列化RectMetaInfo
由於RectMetaInfo中都是基本類型,所以序列化比較簡單。需要注意的是序列化的順序要和反序列化的數據的順序保持一致
//重載序列化 inline QDataStream &operator<<(QDataStream &output , const RectMetaInfo &metaInfo) { output << metaInfo.text << metaInfo.x << metaInfo.y << metaInfo.w << metaInfo.h; return output; } //重載反序列化 inline QDataStream &operator>>(QDataStream &input , RectMetaInfo &metaInfo) { input >> metaInfo.text >> metaInfo.x >> metaInfo.y >> metaInfo.w >> metaInfo.h; return input; }
重載了結構體RectMetaInfo的序列化和反序列化,接下來就要重載結構體ProjectInfo。
由於ProjectInfo包含了QMap容器元素,因此我們需要一個將QMap的元素個數保存起來(添加附加信息)
//重載序列化 inline QDataStream &operator<<(QDataStream &output , const ProjectInfo &pj) { output << pj.projectName << pj.imgPath << pj.annotationMeta << pj.createTime << pj.currentImgIndex; // 附加信息 QMap<int,int> 可以直接被序列化(QMap能否被直接序列化,要看QMap中的類型是否是基本類型,如果是,就可以直接序列化),第一個參數表示第幾個key,第二個參數表示該key下存放的數據的數量 QMap<int,int> collectionPreview; int k_index = 0; foreach(QString key,pj.markCollection.keys()){ collectionPreview[k_index] = pj.markCollection[key].size(); k_index++; } // 序列化QMap時需要注意,頭文件中需要引入 QDataStream 依賴,否則此處報錯 output << collectionPreview; foreach(QString key,pj.markCollection.keys()){ output << key; QList<RectMetaInfo> rectMetas = pj.markCollection[key]; if (rectMetas.size() == 0) continue; for(RectMetaInfo rectMetaInfo : rectMetas){ output << rectMetaInfo; }; } return output ; } //重載反序列化 inline QDataStream &operator>>(QDataStream &input , ProjectInfo &pj) { input >> pj.projectName >> pj.imgPath >> pj.annotationMeta >> pj.createTime >> pj.currentImgIndex; QMap<int,int> collectionPreview; input >> collectionPreview; qDebug() << "反序列化集合的概覽:" << collectionPreview; QMap<QString,QList<RectMetaInfo>> collection; for (int i = 0;i< collectionPreview.size();i++){ QString key; input >> key; if ( collectionPreview[i] == 0 ) continue; QList<RectMetaInfo> rectInfoList; for (int j = 0; j < collectionPreview[i] ; j++){ RectMetaInfo rectMetaInfo; input >> rectMetaInfo; rectInfoList.append(rectMetaInfo); } collection[key] = rectInfoList; } pj.markCollection = collection; return input ; }
上面標注的黃色部分,collectionPreview 就是序列化進去的附加信息,里面的內容是QMap中有多少個key,每個key下有多少個元素。那么在序列化時先將該附加信息,序列化進去,反序列化時先將該附加信息讀取出來,此時反序列化就知道了我的容器內的元素究竟有多少。
但是不管添不添加附加信息,都需要保證序列化/反序列化 元素的順序保持一致,另外一定需要在頭文件中引入QDataStream,否則在序列化一些類型是IDE會報錯
完整代碼:
#pragma execution_character_set("utf-8") #ifndef META_H #define META_H #include <QString> #include <QMetaType> #include <QDebug> #include <QDataStream> //對於自定義數據類型,如果要使用QVariant,必須使用Q_DECLARE_METATYPE注冊。 //https://www.jianshu.com/p/670de4f63689?from=groupmessage //矩形坐標信息 typedef struct RectMetaInfo{ QString text; qreal x; qreal y; qreal w; qreal h; // 默認結構體是無法使用 == 判斷相等的.因此重載運算符 bool operator==(const RectMetaInfo& rect) { return (x == rect.x) && (y == rect.y) && (w == rect.w) && (h == rect.h) && (text == rect.text); } } RectMeta; Q_DECLARE_METATYPE(RectMetaInfo); //qDebug() 不能打印自定義結構體,解決方法就是重載<< 運算符 QDebug inline operator<<(QDebug debug, const RectMetaInfo &rect) { debug << QString("名稱:%1,位置信息:[%2,%3,%4,%5]").arg(rect.text).arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h); return debug; } //重載序列化 inline QDataStream &operator<<(QDataStream &output , const RectMetaInfo &metaInfo) { // 轉換成字符串並保留兩位小數,四舍五入 // x = QString::number(metaInfo.x,'d',2); // y = QString::number(metaInfo.y,'d',2); // w = QString::number(metaInfo.w,'d',2); // h = QString::number(metaInfo.h,'d',2); output << metaInfo.text << metaInfo.x << metaInfo.y << metaInfo.w << metaInfo.h; return output; } //重載反序列化 inline QDataStream &operator>>(QDataStream &input , RectMetaInfo &metaInfo) { input >> metaInfo.text >> metaInfo.x >> metaInfo.y >> metaInfo.w >> metaInfo.h; return input; } //導出方式枚舉 enum export_type{ JSON, XML, MONGO }; //項目信息 typedef struct ProjectInfo{ QString projectName; QString imgPath; QString annotationMeta; QString createTime; QString currentImgIndex; QMap<QString,QList<RectMeta>> markCollection; } Project; Q_DECLARE_METATYPE(ProjectInfo); //重載序列化 inline QDataStream &operator<<(QDataStream &output , const ProjectInfo &pj) { output << pj.projectName << pj.imgPath << pj.annotationMeta << pj.createTime << pj.currentImgIndex; // 附加信息 QMap<int,int> 可以直接被序列化(QMap能否被直接序列化,要看QMap中的類型是否是基本類型,如果是,就可以直接序列化),第一個參數表示第幾個key,第二個參數表示該key下存放的數據的數量 QMap<int,int> collectionPreview; int k_index = 0; foreach(QString key,pj.markCollection.keys()){ collectionPreview[k_index] = pj.markCollection[key].size(); k_index++; } // 序列化QMap時需要注意,頭文件中需要引入 QDataStream 依賴,否則此處報錯 output << collectionPreview; foreach(QString key,pj.markCollection.keys()){ output << key; QList<RectMetaInfo> rectMetas = pj.markCollection[key]; if (rectMetas.size() == 0) continue; for(RectMetaInfo rectMetaInfo : rectMetas){ output << rectMetaInfo; }; } return output ; } //重載反序列化 inline QDataStream &operator>>(QDataStream &input , ProjectInfo &pj) { input >> pj.projectName >> pj.imgPath >> pj.annotationMeta >> pj.createTime >> pj.currentImgIndex; QMap<int,int> collectionPreview; input >> collectionPreview; qDebug() << "反序列化集合的概覽:" << collectionPreview; auto collection = pj.markCollection; for (int i = 0;i< collectionPreview.size();i++){ QString key; input >> key; if ( collectionPreview[i] == 0 ) continue; QList<RectMetaInfo> rectInfoList; for (int j = 0; j < collectionPreview[i] ; j++){ RectMetaInfo rectMetaInfo; input >> rectMetaInfo; rectInfoList.append(rectMetaInfo); } collection[key] = rectInfoList; } return input ; } #endif // META_H
測試代碼:
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QErrorMessage> #include <QMessageBox> #include <QPushButton> #include <QDebug> #include <QButtonGroup> #include <QStandardPaths> #include <QFile> #include <QDir> #include <meta.h> // 結構體頭文件 #define cout qDebug() << "[" <<__FILE__<< " : "<<__LINE__<<"]" QString PROJECT_STORAGE_DIR = QStandardPaths::writableLocation(QStandardPaths::HomeLocation).append("/tt/"); //測試序列化 void MainWindow::serialize() { QDir dir(PROJECT_STORAGE_DIR); if (!dir.exists()){ dir.mkdir(PROJECT_STORAGE_DIR); } // 文件創建時間作為文件名 QString filePath = PROJECT_STORAGE_DIR + "t"+ ".t"; QFile file(filePath); // 文件可讀可寫,文件存在清空文件 file.open(QFile::WriteOnly|QFile::Truncate); QDataStream out(&file); QList<RectMetaInfo> list; for(int i=0;i<3;i++){ RectMetaInfo rect; rect.text = "看雲"+ QString::number(i); rect.x = i+9.987; rect.y = i+7.765; rect.w = i+5.543; rect.h = i+3.321; list << rect; } QList<RectMetaInfo> list1; for(int i=0;i<3;i++){ RectMetaInfo rect; rect.text = "無問"+ QString::number(i); rect.x = i+1.123; rect.y = i+2.234; rect.w = i+3.345; rect.h = i+4.456; list1 << rect; } QMap<QString,QList<RectMetaInfo>> map; map["key"] = list; map["key1"] = list1; qDebug() << list; qDebug() << list1; Project pj; pj.projectName = "哈哈哈"; pj.markCollection = map; out << pj; qDebug() << "序列化完成"; } //測試反序列化 void MainWindow::deserialize() { QString filePath = PROJECT_STORAGE_DIR + "t"+ ".t"; QFile file(filePath); file.open(QFile::ReadOnly); QDataStream in(&file); Project pj; in >> pj; cout << pj.projectName; QMap<QString,QList<RectMetaInfo>> mm = pj.markCollection; foreach(QString key,mm.keys()){ cout << key << "->" ; QList<RectMetaInfo> values = mm[key]; for(RectMetaInfo meta:values){ cout << meta; } } }