Qt 自定义序列化
序列化是信息传输和持久化的基石,用于网络传输的序列化,主流是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; } } }