Qt扫盲-QDataStream 序列化和反序列化理论

news2025/3/15 7:42:00

QDataStream 序列化和反序列化理论

  • 一、概述
  • 二、QDataStream 概述
  • 三、版本控制
  • 四、读取和写入原始二进制数据
  • 五、读写Qt集合类
  • 六、读写其他Qt类
  • 七、使用读事务
  • 八、Qt支持的序列化类型

一、概述

序列化: 指的是将一个内存对象转化成一串字节数据(存储在一个字节数组中),可用于保存到本地文件或网络传输。

反序列化: 就是将字节数据还原成内存对象。

序列化是将对象转换为字节流的过程,可以将对象持久化保存在磁盘或者通过网络传输。
反序列化是将字节流转换为对象的过程,可以将保存或传输的对象重新恢复到内存中。

序列化和反序列化的作用包括:

  • 数据持久化:将对象保存到磁盘中,实现数据的长期存储。例如,将用户的配置信息序列化为文件,以便在下次打开应用程序时可以重新加载。
  • 数据传输:通过网络传输对象,实现不同系统之间的数据交互。
  • 分布式计算:在分布式系统中,对象可以在不同节点之间传输和复制,实现数据共享和计算协作。
  • 缓存和缓存同步:将对象序列化后保存在缓存中,提高系统性能,并可以在不同节点之间进行同步和共享缓存数据。
  • 消息传递:在消息队列等异步通信中,通过序列化和反序列化实现消息的发送和接收。
  • 应用状态保存和恢复:将应用程序的状态保存到文件中,以便在下次启动时恢复到之前的状态。

总之,序列化和反序列化在很多场景中都是非常有用的,可以将对象转化为可以存储、传输或共享的格式,以便在需要时可以重新加载和使用。

在Qt官方里提供了一个这样的工具,就是 QDataStream ,注意这里不是Json格式的序列化

Json文本转Qt对象的可以参考

https://github.com/smurfomen/QSerializer

在这里插入图片描述

二、QDataStream 概述

数据流是编码信息的二进制流,它100%独立于主机的操作系统、CPU或字节顺序。例如,由Windows下的PC机写入的数据流可以被运行Solaris的Sun SPARC读取。

我们还可以使用数据流来读取/写入原始的未编码二进制数据。如果你想要一个“解析”输入流,请参阅QTextStream。
QDataStream类实现了c++基本数据类型的序列化,如char、short、int、char *等。

更复杂数据的序列化是通过将数据分解成基本单元来完成的。

数据流与QIODevice紧密配合。QIODevice表示一种输入/输出介质,可以从中读取数据和向其中写入数据,像文件,socket之类的在QT里都是封装的QIODevice,意味着我们保存在文件里数据同样可以发送到网络中去。

QFile类是I/O设备的一个示例。
示例(将二进制数据写入流):

  QFile file("file.dat");
  file.open(QIODevice::WriteOnly);
  QDataStream out(&file);   // we will serialize the data into the file
  out << QString("the answer is");   // serialize a string
  out << (qint32)42;        // serialize an integer

示例(从流中读取二进制数据):

  QFile file("file.dat");
  file.open(QIODevice::ReadOnly);
  QDataStream in(&file);    // read the data serialized from the file
  QString str;
  qint32 a;
  in >> str >> a;           // extract "the answer is" and 42

写入流的每个项都以预定义的二进制格式写入,该格式根据项的类型而变化。支持的Qt类型包括QBrush, QColor, QDateTime, QFont, QPixmap, QString, QVariant和许多其他。Qt支持所有的类型看后面的备注

对于整数,最好总是将其转换为Qt整数类型进行写入,并将其读回相同的Qt整数类型。这可以确保我们获得所需大小的整数,并使我们免受编译器和平台差异的影响。

枚举可以通过QDataStream序列化,而不需要手动定义流操作符。枚举类使用声明的大小进行序列化。

举个例子,一个char *字符串被写成一个32位整数,等于包含’\0’字节的字符串的长度,后跟包含’\0’字节的字符串的所有字符。当读取char *字符串时,读取4个字节以创建32位长度值,然后读取char *字符串的许多字符,包括’\0’结束符。

初始I/O设备通常在构造函数中设置,但可以使用setDevice()进行更改。如果我们已经到达数据的末尾(或者如果没有I/O设备集),atEnd()将返回true。

三、版本控制

QDataStream的二进制格式自Qt 1.0以来一直在发展,并且可能会继续发展以反映Qt中的变化。当输入或输出复杂类型时,确保读取和写入使用相同版本的流(version())是非常重要的。如果你需要向前和向后兼容,你可以在应用程序中硬编码版本号:

stream.setVersion(QDataStream::Qt_4_0);

如果我们正在生成一种新的二进制数据格式,例如应用程序创建的文档的文件格式,那么我们可以使用QDataStream以可移植格式写入数据。通常,我们会编写一个简短的头文件,其中包含一个魔术字符串和一个版本号,以便为将来的扩展提供空间。例如:

  QFile file("file.xxx");
  file.open(QIODevice::WriteOnly);
  QDataStream out(&file);

  // Write a header with a "magic number" and a version
  out << (quint32)0xA0B0C0D0;
  out << (qint32)123;

  out.setVersion(QDataStream::Qt_4_0);

  // Write the data
  out << lots_of_interesting_data;

然后这样读:

  QFile file("file.xxx");
  file.open(QIODevice::ReadOnly);
  QDataStream in(&file);

  // Read and check the header
  quint32 magic;
  in >> magic;
  if (magic != 0xA0B0C0D0)
      return XXX_BAD_FILE_FORMAT;

  // Read the version
  qint32 version;
  in >> version;
  if (version < 100)
      return XXX_BAD_FILE_TOO_OLD;
  if (version > 123)
      return XXX_BAD_FILE_TOO_NEW;

  if (version <= 110)
      in.setVersion(QDataStream::Qt_3_2);
  else
      in.setVersion(QDataStream::Qt_4_0);

  // Read the data
  in >> lots_of_interesting_data;
  if (version >= 120)
      in >> data_new_in_XXX_version_1_2;
  in >> other_interesting_data;

我们可以在序列化数据时选择要使用的字节顺序。默认设置是大端(MSB优先)。将其更改为小端序会破坏可移植性(除非阅读器也更改为小端序)。我们建议保持此设置,除非我们有特殊要求。

四、读取和写入原始二进制数据

我们可能希望直接从数据流中读取/写入自己的原始二进制数据。可以使用readRawData()将数据从流读入预分配的char *。类似地,可以使用writeRawData()将数据写入流。请注意,数据的任何编码/解码都必须由我们完成。

类似的一对函数是readBytes()和writeBytes()。它们与原始版本的区别如下:readBytes()读取一个quint32,它被认为是要读取的数据的长度,然后将该字节数读入预分配的char *;writeBytes()写入一个包含数据长度的quint32,后面跟着数据。请注意,数据的任何编码/解码(除了长度quint32)都必须由我们完成。

五、读写Qt集合类

Qt容器类也可以序列化为QDataStream。这些包括QList, QLinkedList, QVector, QSet, QHash和QMap。流操作符被声明为类的非成员。

六、读写其他Qt类

除了这里记录的重载流操作符,任何你可能想要序列化到QDataStream的Qt类都将有适当的流操作符声明为类的非成员:

QDataStream &operator<<(QDataStream &, const QXxx &);
QDataStream &operator>>(QDataStream &, QXxx &);

例如,以下是声明为QImage类非成员的流操作符:

QDataStream & operator<< (QDataStream& stream, const QImage& image);
QDataStream & operator>> (QDataStream& stream, QImage& image);

要查看我们喜欢的Qt类是否定义了类似的流操作符,请查看该类文档页面的相关非成员部分。

例如我们去读写自定义的结构体对象

//xx.h
//定义结构体
typedef struct st_mydata
{
    st_mydata() {};

    bool sex;
    int id;
    int age;
    QString address;
    QList<QString> extraInfo;

    friend QDataStream & operator<<(QDataStream &stream, const struct st_mydata & info);
    friend QDataStream & operator>>(QDataStream &stream,  struct st_mydata &info);
} ST_MYDATA;

//xx.cpp
QDataStream & operator<<(QDataStream &stream, const struct st_mydata & info)
{
    stream <<info.id;
    stream <<info.age;

    stream <<info.sex;
    stream <<info.address;
    stream <<info.extraInfo;

    return stream;
}

QDataStream & operator>>(QDataStream &stream, struct st_mydata &info)
{
    stream >>info.id;
    stream >>info.age;

    stream >>info.sex;
    stream >>info.address;
    stream >>info.extraInfo;
    return stream;
}

// 用文件使用--写
void SerialDateUse::testWriteSelfStruct()
{
    ST_MYDATA data;

    data.id = 101;
    data.age = 18;
    data.sex = false;
    data.address = "四川省成都市双流区一号";
    data.extraInfo = QList<QString>{"好人", "非常熬", "牛逼嘞"};

    QFile file("xxxx.bin");
    file.open(QIODevice::WriteOnly);
    QDataStream out(&file);

    out<<data;
    file.flush();
    file.close();
}

void SerialDateUse::testReadSelfStruct()
{
    ST_MYDATA data;
    QFile file("xxxx.bin");
    if(! file.open(QIODevice::ReadOnly))
        return;
    QDataStream in(&file);

    in>>data;
    qDebug()<<"[Info]: ----------------- ";
    qDebug()<<"id: "<<data.id;
    qDebug()<<"age: "<<data.age;
    qDebug()<<"sex: "<<(data.sex ? QString("female") : QString("male"));
    qDebug()<<"address: "<<data.address;
    qDebug()<<"extra info:"<<data.extraInfo.join(",");
}
/* 输入如下
[Info]: -----------------
id:  101
age:  18
sex:  "male"
address:  "四川省成都市双流区一号"
extra info: "好人,非常熬,牛逼嘞"
*/

读写对象也和结构体一样

class SerialDateUse:QObject
{
    Q_OBJECT
public:
    SerialDateUse();

    friend QDataStream & operator<< (QDataStream& stream, const SerialDateUse& info);
    friend QDataStream & operator>> (QDataStream& stream, SerialDateUse& info);

    int age() const;
    void setAge(int newAge);

    QString name() const;
    void setName(const QString &newName);

    QList<QString> infos() const;
    void setInfos(const QList<QString> &newInfos);

private:
    int m_age = 0;
    QString m_name = "None";
    QList<QString> m_infos;
};
#include "SerialDateUse.h"

SerialDateUse::SerialDateUse()
{

}

int SerialDateUse::age() const
{
    return m_age;
}

void SerialDateUse::setAge(int newAge)
{
    m_age = newAge;
}

QString SerialDateUse::name() const
{
    return m_name;
}

void SerialDateUse::setName(const QString &newName)
{
    m_name = newName;
}

QList<QString> SerialDateUse::infos() const
{
    return m_infos;
}

void SerialDateUse::setInfos(const QList<QString> &newInfos)
{
    m_infos = newInfos;
}

QDataStream &operator<<(QDataStream &stream, const SerialDateUse &info)
{
    stream<<info.m_age;
    stream<<info.m_name;
    stream<<info.m_infos;

    return stream;
}

QDataStream &operator>>(QDataStream &stream, SerialDateUse &info)
{
    stream>>info.m_age;
    stream>>info.m_name;
    stream>>info.m_infos;

    return stream;
}

使用代码

//写数据
SerialDateUse ss;
ss.setAge(12);
ss.setName("张洪源");
ss.setInfos(QList<QString>{("xxx"), ("YYY")});

QFile file("file.bin");
if (!file.open(QIODevice::WriteOnly))
{
        qDebug()<<"File Write Error";
}
QDataStream out(&file);
    
out<<ss;
file.flush();
file.close();

//读数据
SerialDateUse read;

QFile readfile("file.bin");
if (!readfile.open(QIODevice::ReadOnly))
{
        qDebug()<<"File Read Error";
}
QDataStream read_out(&readfile);

read_out>>read;

qDebug()<<read.age();
qDebug()<<read.name();
qDebug()<<read.infos();
readfile.close();

七、使用读事务

当数据流在异步设备上运行时,数据块可以在任意时间点到达。QDataStream类实现了一种事务机制,该机制提供了使用一系列流操作符自动读取数据的能力。例如,你可以通过使用连接到readyRead()信号的插槽中的事务来处理来自套接字的不完整读取:

  in.startTransaction();
  QString str;
  qint32 a;
  in >> str >> a; // try to read packet atomically

  if (!in.commitTransaction())
      return;     // wait for more data

如果没有接收到完整的数据包,该代码将流恢复到初始位置,之后需要等待更多的数据到达。

八、Qt支持的序列化类型

QDataStream类允许我们序列化本节中列出的从版本18开始的Qt数据类型。
在读写时,最好将整数转换为Qt整数类型,如qint16或quint32。这确保了无论应用程序碰巧运行在什么底层平台和体系结构上,我们始终确切地知道正在读取和写入的整数的大小,确实因为不同平台的 的分配大小还是有区分的。

  • bool
  • qint8
  • qint16
  • qint32
  • qint64
  • quint8
  • quint16
  • quint32
  • quint64
  • float
  • double
  • const char *
  • QBitArray
  • QBrush
  • QByteArray
  • QColor
  • QCursor
  • QDate
  • QDateTime
  • QEasingCurve
  • QFont
  • QGenericMatrix
  • QHash<Key, T>
  • QIcon
  • QImage
  • QKeySequence
  • QLinkedList
  • QList
  • QMap<Key, T>
  • QMargins
  • QMatrix4x4
  • QPair<T1, T2>
  • QPalette
  • QPen
  • QPicture
  • QPixmap
  • QPoint
  • QQuaternion
  • QRect
  • QRegExp
  • QRegularExpression
  • QRegion
  • QSize
  • QString
  • QTime
  • QTransform
  • QUrl
  • QVariant
  • QVector2D
  • QVector3D
  • QVector4D
  • QVector

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1103764.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FSDP(Fully Sharded Data Parallel)

完全分片数据并行 (FSDP) &#xff0c;它将AI 模型的参数分片到数据并行工作器上&#xff0c;并且可以选择将部分训练计算卸载到 CPU。顾名思义&#xff0c;FSDP 是一种数据并行训练算法。尽管参数被分片到不同的GPU&#xff0c;但每个微批次数据的计算仍然是每个 GPU Worker 本…

C++ - 一些特殊类的设计

前言 我们在日常写项目的过程当中&#xff0c;肯定会遇到各种各样的需求&#xff0c;那么也就要求我们要写各种各样的类。本篇博客当中&#xff0c;就一些常用的特殊类进行介绍和实现。 不能被拷贝的类 关于实例化类拷贝&#xff08;对象的拷贝&#xff09;一般就是两个场景&…

6.DApp-用Web3实现前端与智能合约的交互

题记 用Web3实现前端与智能合约的交互&#xff0c;以下是操作流程和代码。 准备ganache环境 文章地址&#xff1a;4.DApp-MetaMask怎么连接本地Ganache-CSDN博客 准备智能合约 文章地址&#xff1a; 2.DApp-编写和运行solidity智能合约-CSDN博客 编写index.html文件 <!…

简单测试一下 展锐的 UDX710 性能

最近在接触 联通5G CPE VN007 &#xff0c;发现使用的是 展锐的Unisoc UDX710 CPU&#xff0c;正好简单的测试一下这颗CPU CPU信息 UDX710 是一颗 双核 ARM Cortex-A55 处理器&#xff0c;主频高达 1.35GHz processor : 0 BogoMIPS : 52.00 Features : fp…

ARM资源记录《AI嵌入式系统:算法优化与实现》第八章(暂时用不到)

1.CMSIS的代码 书里给的5&#xff0c;https://github.com/ARM-software/CMSIS_5 现在有6了&#xff0c;https://github.com/ARM-software/CMSIS_6 这是官网的书&#xff0c;介绍cmsis函数的https://arm-software.github.io/CMSIS_5/Core/html/index.html 2.CMSIS介绍 Cort…

Git命令在线练习网址--非常友好的提示及动画展示

Git命令在线练习 https://learngitbranching.js.org/ 举个栗子: 在练习时会给你相应提示,你可以按照相应步骤进行操作,并且每一步都有动画演示

A股风格因子看板 (2023.10 第05期)

该因子看板跟踪A股风格因子&#xff0c;该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子&#xff0c;用以分析市场风格切换、组合风格暴露等。 今日为该因子跟踪第05期&#xff0c;指数组合数据截止日2023-09-30&#xff0c;要点如下 近1年A股风格因子检验统…

Windows10 Docker 安装教程

Docker Desktop是什么&#xff1f; Docker Desktop是适用于Windows的Docker桌面&#xff0c;是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序&#xff0c;为构建、交付和运行dockerized应用程序提供易于使用的开发环境。Docker Desktop for Windows 使用 …

手机应用app打开游戏显示连接服务器失败是什么原因?排查解决方案?

亲爱的同学们&#xff0c;有时候我们在使用手机设备时&#xff0c;可能会遇到一个很头疼的问题——连接服务器失败。这个问题不仅让我们感到困扰&#xff0c;还影响到了我们的用户体验。那么&#xff0c;我们究竟能如何解决这个问题呢&#xff1f;今天&#xff0c;笔者就和大家…

一百九十、Hive——Hive刷新分区MSCK REPAIR TABLE

一、目的 在用Flume采集Kafka中的数据直接写入Hive的ODS层静态分区表后&#xff0c;需要刷新表&#xff0c;才能导入分区和数据。原因很简单&#xff0c;就是Hive表缺乏分区的元数据 二、实施步骤 &#xff08;一&#xff09;问题——在Flume采集Kafka中的数据写入HDFS后&am…

Scrum 敏捷管理流程图及敏捷管理工具

​敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示&#xff0c;以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例&#xff1a; 转自&#xff1a;Leangoo.com 免费敏捷工具 这个流程图涵盖了Scrum框架的主要阶段和活动&#xff0c;其中包括…

CSS详细解析二

05-显示模式 显示模式&#xff1a;标签&#xff08;元素&#xff09;的显示方式。 作用&#xff1a;布局网页的时候&#xff0c;根据标签的显示模式选择合适的标签摆放内容。 块级元素 特点&#xff1a; 独占一行 宽度默认是父级的100% 添加宽高属性生效 行内元素 特…

RK3399平台开发系列讲解(驱动篇)Regulator Framework

🚀返回专栏总目录 文章目录 一、Regulator驱动二、consumer接口函数三、用户空间sysfs接口沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 要简单介绍下系统的Regulator Framework。 Regulator翻译为”调节器”,分为电压调节器(voltage regulator)和电流调节器(cu…

leetcode209. 长度最小的子数组 [滑动窗口] 待更正

题目 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 1&#xff1a; 输入…

金融数学方法:有限差分法

目录 1.原理介绍 1.1 有限差分法介绍 1.2 有限差分法步骤 2.案例分析 2.1 问题重述 2.2 问题求解 1.原理介绍 1.1 有限差分法介绍 有限差分法是一种常用的数值计算方法&#xff0c;用于求解偏微分方程或常微分方程的数值解。它的基本思想是将连续的空间区域离散化为有限…

河北建筑模板厂家供应-能强优品木业

随着我国建筑业的蓬勃发展&#xff0c;建筑模板作为一项基础性的建筑材料&#xff0c;在市场需求中占据着举足轻重的地位。在河北建筑模板供应市场中&#xff0c;广西贵港市能强优品木业有限公司以其实力雄厚、品质卓越、性价比极高的特点脱颖而出&#xff0c;成为众多建筑企业…

Sentinel-1主动微波数据下载

Sentinel-1主动微波数据下载 这里主要介绍主动微波数据用于反演陆地参数的情况 GRD文件类型包含多视强度数据&#xff0c;该强度与后向散射系数相关&#xff0c;适合反演土壤水分 IW模式是陆地上的主要采集模式 VVVH的极化方式适合观测陆地 有关各个参数的介绍&#xff0c;使用…

Redis LFU缓存淘汰算法

前言 Redis 在 4.0 版本之前的缓存淘汰算法&#xff0c;只支持 random 和 lru。random 太简单粗暴了&#xff0c;可能把热点数据给淘汰掉&#xff0c;一般不会使用。lru 比 random 好一点&#xff0c;会优先淘汰最久没被访问的数据&#xff0c;但是它也有一个缺点&#xff0c;…

JavaScript系列从入门到精通系列第二十篇:使用工厂方法创建JavaScript对象,JavaScript构造函数详解,JavaScript类概念的介绍

文章目录 一&#xff1a;使用工厂方法创建对象 1&#xff1a;原始写法 2&#xff1a;工厂方式 3&#xff1a;结果验证 二&#xff1a;构造函数 1&#xff1a;什么是构造函数 2&#xff1a;构造函数和普通函数的区别 3&#xff1a;构造函数的执行流程 三&#xff1a;类…

SpringBoot基础详解

目录 SpringBoot自动配置 基于条件的自动配置 调整自动配置的顺序 纷杂的SpringBoot Starter 手写简单spring-boot-starter示例 SpringBoot自动配置 用一句话说自动配置&#xff1a;EnableAutoConfiguration借助SpringFactoriesLoader将标准了Configuration的JavaConfig类…