开发者

C++数据序列化方式(自定义结构体的保存和读取)

开发者 https://www.devze.com 2023-11-20 13:35 出处:网络 作者: 庐州李大爷
目录C++数据序列化常用的c++序列化方法1、 什么是序列化?2、 为什么要序列化?好处在哪里?3、最常用的两种序列化方案使用心得总结C++数据序列化
目录
  • C++数据序列化
  • 常用的c++序列化方法
    • 1、 什么是序列化?
    • 2、 为什么要序列化?好处在哪里?
    • 3、最常用的两种序列化方案使用心得
  • 总结

    C++数据序列化

    碰到一个需求,结构体数据需要保存下来,以便下次程序打开后再次加载。结构体存在嵌套。

    查找资料,确认可以通过文件的读写进行操作,FILE,fread和fwrite可以实现,以下是测试代码(使用模板实现相关功能)

    核心部分是

    template <class T>
    void write_dataToFile( T *t, const char *filePath)
    template <class T>
    void read_dataFromFile(T* t, const char *filePath)
    udkBTzPpm

    两个文件,主要是利用了FILE的读写操作,注意,此方法可能存在瑕疵,文件的size依赖于平台,如果在A平台生成(写)的文件,在B平台可能会解析(读)错误。

    #include <QApplication>
    #include <QtDebug>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    enum ENUMT{
        DI = 0,
        DO,
        DP
    };
    struct Info_C{
        QString name = "1212";
        double age = 12.32323;
    };
    struct Info{
        QString name = "ddsdsd";
        int age = 0;
        ENUMT enumT = DI;
        Info_C info_c;
    };
    template <class T>
    void write_dataToFile(www.devze.com T *t, const char *filePath)
    {
        FILE *fp = nullptr;
        fp = fopen(filePath, "w+");
        if (nullptr == fp)
        {
            printf("open failure ,filePath : %s, errno: %d", filePath, errno);
            qDebug("open failure ,filePath : %s, errno: %d", filePath, errno);
            return;
        }
        fwrite(t, sizeof(T), 1, fp);
        fclose(fp);
    }
    template <class T>
    void read_dataFromFile(T* t, const char *filePath)
    {
        FILE *fp = fopen(filePath, "r");
        if (nullptr == fp)
        {
            printf("open failure ,filePath : %s, errno: %d", filePath, errno);
            qDebug("open failure ,filePath : %s, errno: %d", filePath, errno);
            return;
        }
        fread(t, sizeof(T), 1, fp);
        fclose(fp);
    }
    using namespace hv;
    int main(int argc, char *argv[])
    {
        //读写基本类型
        Info writeInfo;
        writeInfo.name = "baseType";
        writeInfo.age = 18;
        writeInfo.enumT = DO;
        write_dataToFile(&writeInfo,"./info.txt");
        Info readInfo;
        read_dataFromFile(&readInfo,"./info.txt");
        qDebug()<<readInfo.name<<readInfo.age<<readInfo.enumT;
        //读写容器,vector
        QVector<QString> vec;
        vec.push_back("vec_base1");
        vec.push_back("vec_base2");
        vec.push_back("vec_base3");
        write_dataToFile(&vec,"./info1.txt");
        QVector<QString> vec11;
        read_dataFromFile(&vec11,"./info1.txt");
        qDebug("read:%d\n", vec11.count());
        if(vec11.count() > 2)
            qDebug()<<vec11.at(2);
        //读写容器结构体-包含嵌套
        QVector<Info> vec_stu;
        Info stu;
        stu.name = "vec_stu1";
        stu.age = 18;
        stu.enumT = DO;
        vec_http://www.devze.comstu.push_back(stu);
        Info stu1;
        stu1.name = "vec_stu2";
        stu1.age = 100;
        stu1.enumT = DI;
        stu1.info_c.age = 43.434;
        stu1.info_c.name = "vec_stu_stu";
        vec_stu.push_back(stu1);
        write_dataToFile(&vec_stu,"./info11.txt");
        QVector<Info> vec_stu11;
        read_dataFromFile(&vec_stu11,"./info11.txt");
        qDebug("read vector stu:%d\n", vec_stu11.count());
        if(vec_stu11.count() > 1){
            qDebug()<<vec_stu11.at(1).name<<vec_stu11.at(1).info_c.age<<vec_stu11.at(1).info_c.name;
        }
        return 0;
    }

    结果示意图,可正常的进行读取。

    C++数据序列化方式(自定义结构体的保存和读取)

    常用的c++序列化方法

    1、 什么是序列化?

    程序员在编写应用程序的时候往往需要将程序的某些数据存储在内存中,然后将其写入某个文件或是将它传输到网络中的另一台计算机上以实现通讯。这个将 程序数据转化成能被存储并传输的格式的过程被称为“序列化”(Serialization),而它的逆过程则可被称为“反序列化” (Deserialization)。

    简单来说,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它根据流重构对象。这两个过程结合起来,可以轻 松地存储和传输数据。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。

    总结:

    序列化:将对象变成字节流的形式传出去。

    反序列化:从字节流恢复成原来的对象。

    序列化简化了对象的保存和载入,为对象提供了持久性。但是,序列化本身仍具有一定的局限性。

    由于序列化一次从文件中载入所有对象,因此,它不适合于大文件编辑器和数据库。对于数据库和大文件编辑器,它们每次只是从文件中读入一部分。此时,就不应该采用文档的序列化机制来直接读取和保存文件了。

    另外,使用外部文件格式(预先定义的文件格式而不是本应用程序定义的文件格式)的程序一般也不使用文档的序列化。

    2、 为什么要序列化?好处在哪里?

    简单来说,对象序列化通常用于两个目的:

    (1) 将对象存储于硬盘上 ,便于以后反序列化使用

    (2)在网络上传送对象的字节序列

    对象序列化的好处在哪里?网络传输方面的便捷性、灵活性就不说了,这里举个我们经常可能发生的需求:你 有一个数据结构,里面存储的数据是经过很多其它数据通过非常复杂的算法生成的,由于数据量很大,算法又复杂,因此生成该数据结构所用数据的时间可能要很久 (也许几个小时,甚至几天),生成该数据结构后又要用作其它的计算,那么你在调试阶段,每次运行个程序,就光生成数据结构就要花上这么长的时间,无疑代价 是非常大的。

    如果你确定生成数据结构的算法不会变或不常变,那么就可以通过序列化技术生成数据结构数据存储到磁盘上,下次重新运行程序时只需要从磁盘上读 取该对象数据即可,所花费时间也就读一个文件的时间,可想而知是多么的快,节省了我们的开发时间。

    3、最常用的两种序列化方案使用心得

    3.1、Google Protocol Buffers

    protobuf相对而言效率应该是最高的,不管是安装效率还是使用效率,protobuf都很高效,而且protobuf不仅用于C++序列化,还可用于Java和python的序列化,使用范围很广。但在使用过程中要注意两个问题:

    (1)protobuf支持的数据类型不是很丰富

    protobuf属于轻量级的,因此不能支持太多的数据类型,下面是protobuf支持的基本类型列表,一般都能满足需求,不过在选择方案之前,还是先看看是否都能支持,以免前功尽弃。同样该表也值得收藏,作为我们在定义类型时做参考。

    (2)protobuf不支持二维数组(指针),不支持STL容器序列化

    这个缺陷挺大,因为稍复杂点的数据结构或类结构里出现二维数组、二维指针和STL容器(set、list、map等)很频繁,但因为 protobuf简单的实现机制,只支持一维数组和指针(用repeated修饰符修饰),不能使用repeated repeated来支持二维数组, 也不支持STL,因此在选择该方案之前,一定 要确保你的数据结构里没有这些不支持的类型。

    (3)protobuf嵌套后会改变类名称

    protobuf支持类的嵌套,即在一个自定义类型中可以定义另一个自定义类型,但注意嵌套的自定义类型在经过protobuf处理后生成的类名称并不是你定义的类名称,而是加上了外层的类名称作为前缀,下面举一个简单的例子:

    message DFA { 
        required int32 _size = 1; 
        message accept_pair { 
         required bool is_accept_state = 1; 
         required bool is_strict_end = 2; 
         optional string app_name = 3; 
        } 
        repeated accept_pair accept_states = 2; 
      }

    那么嵌套中的accept_pair 生成后的类不是accept_pair 而是DFA_accept_pair 。如果不想改类名称,将accept_pair 拿到外面与DFA平行定义即可。

    3.2 Boost.Serialization

    Boost库是个很庞大的库,功能非常丰富,序列化只是其中的一个小分支,但为了使用Boost的序列化方案,你需要安装整个Boost库,所花费的磁盘空间和时间都很多,同样支持的序列化功能也很强大,既支持二维数组(指针),也支持STL容器,更不需要我们用某种特殊python的格式重新定义我们的类结构,其非侵入的性质使得我们无须改动已有的类结构即可序列化,这时非常赞的一个性质。但是由于体积庞大,安装复杂,如果只是简单的序列化,没必要使用该方案,只有protobuf不能满足你的需求时,才应该考虑该方案。

    • text_oarchive:文本序列化
    • binary_oarchive:二进制系列化

    例子:

    #include <IOStream>
    #include <boost\archive\text_oarchive.hpp>
    #include <boost\archive\text_iarchive.hpp>
    //#include <boost\archive\binary_oarchive.hpp>
    //#include <boost\archive\binary_iarchive.hpp>
    #include <string>
    #include <fstream>
    void Save()
    {
     /*打开Test.bin 此种方法是利用构造函数打开*/
     std::ofstream file("Test.bin");
     /*定义oa为二进制存储类型*/
     boost::archive::text_oarchive oa(file);
     //boost::archive::binary_oarchive oa(file);
     int nNum = 0;
     std::cout << "输入int型" << std::endl;
     std::cin >> nNum;
     float fFloat = 0.0f;
     std::cout << "输入float型" << std::endl;
     std::cin >> fFloat;
     oa << nNum << fFloat;
     file.close();
    }
    void Load()
    {
     std::ifstream file("Test.bin");
     /*定义ia为二进制读取类型*/
     boost::archive::text_iarchive ia(file);
    python //boost::archive::binary_iarchive ia(file);
     /*读取顺序应和存储顺序相同 类型也需要相同*/
     int n = 0;
     float f = 0.0f;
     ia >> n >> f;
     std::cout << n << " " << f << std::endl;
     file.close();
    }
    int main()
    {
     int nTemp = 0;
     std::cout << "1.输入并存储 2.读取并显示" << std::endl;
     std::cin >> nTemp;
     switch (nTemp)
     {
     case 1:
      Save();
      break;
     case 2:
      Load();
      break;
     default:
      break;
     }
     while (1);
     return 0;
    }

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号