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


    一、概述

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

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

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

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

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

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

    在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
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

      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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    写入流的每个项都以预定义的二进制格式写入,该格式根据项的类型而变化。支持的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);
    
    • 1

    如果我们正在生成一种新的二进制数据格式,例如应用程序创建的文档的文件格式,那么我们可以使用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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后这样读:

      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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    我们可以在序列化数据时选择要使用的字节顺序。默认设置是大端(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 &);
    
    • 1
    • 2

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

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

    要查看我们喜欢的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: "好人,非常熬,牛逼嘞"
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    读写对象也和结构体一样

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    使用代码

    //写数据
    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    七、使用读事务

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

      in.startTransaction();
      QString str;
      qint32 a;
      in >> str >> a; // try to read packet atomically
    
      if (!in.commitTransaction())
          return;     // wait for more data
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    八、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
    • QIcon
    • QImage
    • QKeySequence
    • QLinkedList
    • QList
    • QMap
    • QMargins
    • QMatrix4x4
    • QPair
    • QPalette
    • QPen
    • QPicture
    • QPixmap
    • QPoint
    • QQuaternion
    • QRect
    • QRegExp
    • QRegularExpression
    • QRegion
    • QSize
    • QString
    • QTime
    • QTransform
    • QUrl
    • QVariant
    • QVector2D
    • QVector3D
    • QVector4D
    • QVector
  • 相关阅读:
    分享一个查询OpenAI Chatgpt key余额查询的工具网站
    如何在JavaScript中使用高阶函数
    如何优雅的创建线程
    容器+虚拟机双引擎,ZStack Edge云原生超融合打通业务最后一公里
    [HFCTF2020]EasyLogin-1|JWT身份伪造
    java毕业设计网上书城系统源码+lw文档+mybatis+系统+mysql数据库+调试
    如何开展性能测试,你知道吗?
    抖音热搜榜上榜操作策略
    Apollo规划代码ros移植-动态障碍物处理(一)
    引擎入门 | Unity UI简介–第1部分(9)
  • 原文地址:https://blog.csdn.net/qq_43680827/article/details/133848077