精华内容
下载资源
问答
  • C++ 序列化和反序列化
    千次阅读
    2020-07-07 15:04:16

    序列化

    1、背景

    • 1、在TCP的连接上,它传输数据的基本形式就是二进制流,也就是一段一段的1和0。

    • 2、在一般编程语言或者网络框架提供的API中,传输数据的基本形式是字节,也就是Byte。一个字节就是8个二进制位,8个Bit。

      • 二进制流和字节流本质上是一样的。对于我们编写的程序来说,它需要通过网络传输的数据是结构化的数据,比如,一条命令、一段文本或者一条消息。对应代码中,这些结构化的数据都可以用一个类或者一个结构体来表示。
    • 序列化的用途除了用于在网络上传输数据以外,

    • 将结构化数据保存在文件中(将对象存储于硬盘上),因为文件内保存数据的形式也是二进制序列。

    问题:
    在内存里存放的任何数据,它最基础的存储单元也是二进制比特,也就是说,我们应用程序操作的对象,它在内存中也是使用二进制存储的,既然都是二进制,为什么不能直接把内存中,对象对应的二进制数据直接通过网络发送出去,或者保存在文件中呢?为什么还需要序列化和反序列化呢?

    • 内存里存的东西,不通用, 不同系统, 不同语言的组织可能都是不一样的, 而且还存在很多引用,指针,并不是直接数据块。内存中的对象数据应该具有语言独特性,例如表达相同业务的User对象(id/name/age字段),Java和PHP在内存中的数据格式应该不一样的,如果直接用内存中的数据,可能会造成语言不通。只要对序列化的数据格式进行了协商,任何2个语言直接都可以进行序列化传输、接收。
    • 一个数据结构,里面存储的数据是经过非常多其他数据通过非常复杂的算法生成的,因为数据量非常大,因此生成该数据结构所用数据的时间可能要非常久,生成该数据结构后又要用作其他的计算,那么你在调试阶段,每次执行个程序,就光生成数据结构就要花上这么长的时间。假设你确定生成数据结构的算法不会变或不常变,那么就能够通过序列化技术生成数据结构数据存储到磁盘上,下次又一次执行程序时仅仅须要从磁盘上读取该对象数据就可以,所花费时间也就读一个文件的时间。
    • 虽然都是二进制的数据,但是序列化的二进制数据是通过一定的协议将数据字段进行拼接。第一个优势是:不同的语言都可以遵循这种协议进行解析,实现了跨语言。第二个优势是:这种数据可以直接持久化到磁盘,从磁盘读取后也可以通过这个协议解析出来。

    2、定义

    要想使用网络框架的API来传输结构化的数据,必须得先实现结构化的数据与字节流之间的双向转换。这种将结构化数据转换成字节流的过程,称为序列化,反过来转换,就是反序列化。

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

    3、序列化评价指标

    • 1、可读性
      • 序列化后的数据最好是易于人类阅读的
    • 2、实现复杂度
      • 实现的复杂度是否足够低
    • 3、性能
      • 序列化和反序列化的速度越快越好
    • 4、信息密度
      • 序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好
    03   | 08 7a 68 61 6e 67 73 61 6e | 17 | 01
    User |    z  h  a  n  g  s  a  n  | 23 | true
    
    • 1.首先我们需要标识一下这个对象的类型,这里面我们用一个字节来表示类型,比如用 03 表示这是一个 User 类型的对象。
    • 2.我们约定,按照 name、age、married 这个固定顺序来序列化这三个属性。按照顺序,第一个字段是 name,我们不存字段名,直接存字段值“zhangsan”就可以了,由于名字的长度不固定,我们用第一个字节 08 表示这个名字的长度是 8
      个字节,后面的 8 个字节就是 zhangsan。
    • 3.第二个字段是年龄,我们直接用一个字节表示就可以了,23 的 16 进制是 17 。
    • 4.最后一个字段是婚姻状态,我们用一个字节来表示,01 表示已婚,00 表示未婚,这里面保存一个 01。

    同样的一个User对象,JSON序列化后({"name":"zhangsan","age":"23","married":"true"})

    • JSON序列化后需要47个字节,专用的序列化方法只要12个字节就够了。
    • 专用的序列化方法显然更高效,序列化出来的字节更少,在网络传输过程中的速度也更快。但缺点是,需要为每种对象类型定义专门的序列化和反序列化方法,实现起来太复杂了,大部分情况下是不划算的。

    4、序列化实例

    案例1:

    //Srlz1.cpp: 将一个类的一个对象序列化到文件
       #include <iostream>
       #include <fcntl.h>
       #include <vector>
       #include <stdio.h>
       using namespace std;
     //定义类CA
       //数据成员:int x;
      //成员函数:Serialize:进行序列化函数
      //             Deserialize反序列化函数
      //             Show:数据成员输出函数
      class CA
      {
          private:
              int x;  //定义一个类的数据成员。       public:
              CA()    //默认构造函数
              {
                  x = ;
              }
              CA(int y):x(y)    //定义构造函数,用初始化列表初始化数据成员
              {
              }
              virtual ~CA()    //析构函数
              {
              }
        public:
              //序列化函数:Serialize
              //成功,返回0,失败,返回0;
              int Serialize(const char* pFilePath) const
              {
                  int isSrlzed = -;
                  FILE* fp;   //define a file pointer
                  //以读写方式打开文件,并判断是否打开;
             if ((fp = fopen(pFilePath, "w+")) == NULL)
                 {
                      printf("file opened failure\n");
                      return -;  //若打开失败,则返回-1;
                  }
                  //调用fwrite函数,将对象写入文件;
             isSrlzed = fwrite(&x, sizeof(int), , fp);           //判断写入是否成功;
             if ((- == isSrlzed) || ( == isSrlzed))
                  {
                      printf("Serialize failure\n");
                      return -;  //若写入失败,则返回-1;
                  }
                  if(fclose(fp) != ) //关闭文件
                  {
                      printf("Serialize file closed failure.\n");
                      return -;
                  }
                  printf("Serialize succeed.\n");
                  return ;   //序列化成功,返回0;
              }           //反序列化函数:
              //成功,返回0,失败,返回-1;
              int Deserialize(const char* pFilePath)
              {
                  int isDsrlzed = -;
                  FILE* fp;
                  //以读写方式打开文件,并判断是否打开;
                  if ((fp = fopen(pFilePath, "r+")) == NULL)
                  {
                      printf("file opened failure.\n");
                      return -;
                  }
                     //调用fread函数,读取文件中的对象 ;
             isDsrlzed = fread(&x, sizeof(int), , fp);
             //判断是否成功读入
                  if ((- == isDsrlzed)||( == isDsrlzed))
                  {
                      printf("Deserialize failure.\n");
                      return -;    //若读取失败,则返回-1;
                  }
                  if(fclose(fp) != )
                  {
                      printf("Deserialize file closed failure.\n");
                      return -;
                  }
                  printf("Deserialize succeed.\n");
                  return ;        //反序列化成功,则返回0;
           }           //成员对象输出显示函数
              void Show()
              {
                  cout<< "in Show():"<< x << endl;
              }
      };
    
     int main(int argc, char const *argv[])
     {
       CA as();    //定义一个类对象,并初始化;     
       //调用序列化函数,将对象as序列化到文件data.txt中;
       as.Serialize("data.txt");   
       CA ad;        //定义一个类对象,用来记录反序列化对象
       //调用反序列化函数,将文件data.txt中的对象反序列化到ad对象;
       ad.Deserialize("data.txt");    
       ad.Show();    //调用输出显示函数;    
     	return ;  
     }
    

    更详细的案例,简单注释:
    1、CharVec.h : 定义一个 vector < char> 类型字节数组,也就是一个定义的容器,为下面的DataStream中存放数据提供接口:

    #ifndef CHARVEC_H
    #define CHARVEC_H 
    #include <memory>
    class CharVec{
    public:
        CharVec();    
        CharVec(const CharVec &vec);   
        CharVec &operator =(const CharVec &vec);   
        ~CharVec();
        
        bool operator ==(const CharVec &vec) const;
        
        size_t size() const;//vector里实际存放数据的大小
        size_t capacity() const;//capacity是vector的内存分配大小
        char *begin() const;
        char *end() const;
        
        void push(const char *data, int len);
        void push(const std::string &str);
        void push(char c);
        void removeFromFront(int len);
        void clear();
    private:
        void checkAndAlloc();
        void reallocate();
        void free();
        std::pair<char *, char *> allocAndCopy(char *begin, char *end);
    private:
        char *m_Elements;   // 首元素
        char *m_FirstFree;  // 最后一个实际元素之后的位置
        char *m_Cap;        // 分配内存末尾之后的位置
        std::allocator<char> m_Allocator;  // 内存分配器
    };
    #endif // CHARVEC_H
    

    2、CharVec.cpp :对CharVec.h 声明的函数进行定义

    // CharVec.cpp
    #include "CharVec.h"
    CharVec::CharVec() :m_Elements(nullptr), m_FirstFree(nullptr),m_Cap(nullptr)
    {}//构造函数
    CharVec::CharVec(const CharVec &vec)//拷贝构造
    {
        auto newData = allocAndCopy(vec.begin(), vec.end());//allocAndCopy分配空间,并且初始化
        m_Elements  = newData.first;
        m_FirstFree = newData.second;
        m_Cap       = newData.second;
    }
    CharVec &CharVec::operator =(const CharVec &vec)//=重载
    {
        auto newData = allocAndCopy(vec.begin(), vec.end());
        free();
        m_Elements  = newData.first;
        m_FirstFree = newData.second;
        m_Cap       = newData.second;
        return *this;
    }
    CharVec::~CharVec()//析构
    {
        free();
    }
     
    bool CharVec::operator ==(const CharVec &vec) const//==重载
    {
        if (m_Elements == vec.m_Elements &&
                m_FirstFree == vec.m_FirstFree &&
                m_Cap == vec.m_Cap) {
            return true;
        }
        return false;
    }
    size_t CharVec::size() const//当前元素数目
    {
        return m_FirstFree - m_Elements;
    }
    size_t CharVec::capacity() const//容器总的空间大小
    {
        return m_Cap - m_Elements;
    }
    
    char *CharVec::begin() const
    {
        return m_Elements;
    }
    char *CharVec::end() const
    {
        return m_FirstFree;
    }
    void CharVec::push(const char *data, int len)
    {
        if (len <= 0) {
            return ;
        }
        for (int i = 0; i < len; ++i) {
            push(data[i]);
        }
    }
    void CharVec::push(const std::string &str)
    {
        push(str.c_str(), str.size());
    }
    void CharVec::push(char c)
    {
        checkAndAlloc();
        m_Allocator.construct(m_FirstFree++, c);
    }
    void CharVec::removeFromFront(int len)//从m_Element开始释放掉len长度的数据。
    {
        if (len > size()) {
            return ;
        }
        char *from = m_Elements;
        char *to = m_Elements + len;
        m_Elements += len;
        for (int i = 0; i < len; ++i) {
            m_Allocator.destroy(--to);
        }
        m_Allocator.deallocate(from, m_Elements - from);
    }
    void CharVec::clear()//容器清空操作
    {
        free();
        m_Elements = nullptr;
        m_FirstFree = nullptr;
        m_Cap = nullptr;
    }
    //checkAndAlloc()会先判断size是不是和capacity相等,
    //然后调用reallocate进行内存的分配,重新分配的空间是原来的2倍,
    //然后数据转移,使用std::move而不是拷贝可以提高效率。
    void CharVec::checkAndAlloc()
    {
        if (size() == capacity()) {
            reallocate();
        }
    }
    void CharVec::reallocate()//类似vector的扩容操作
    {
        auto newCapacity = size() ? 2 * size() : 1;//重新分配的空间是原来的2倍
        auto newData = m_Allocator.allocate(newCapacity);//allocate分配空间
        auto dest = newData;
        auto ele = m_Elements;
       for (size_t i = 0; i != size(); ++i) {
            m_Allocator.construct(dest++, std::move(*ele++));//construct初始构造
        }
        free();
        m_Elements  = newData;
        m_FirstFree = dest;
        m_Cap       = m_Elements + newCapacity;
    }
    void CharVec::free()
    {
        if (m_Elements) {
            for (auto p = m_FirstFree; p != m_Elements;) {
                m_Allocator.destroy(--p);//destroy析构对象,此时空间还是可以使用
            }
            m_Allocator.deallocate(m_Elements, m_Cap - m_Elements);//deallocate回收空间
        }
    }
    
    std::pair<char *, char *> CharVec::allocAndCopy(char *begin, char *end)
    {
       auto startPos = m_Allocator.allocate(end - begin);
       return {startPos, std::uninitialized_copy(begin, end, startPos)};
    }
    

    3、DataHeader类的声明:定义id,及headerlen,totalLen相关客户属性

    // DataHeader.h
    
    #ifndef DATAHEADER_H
    #define DATAHEADER_H
    
    struct DataHeader
    {
        DataHeader(int id = 0);
        bool operator==(const DataHeader &header);
        void reset();
        const static int s_HeaderLen = 3 * sizeof(int);
        int m_Id;
        int m_HeaderLen;
        int m_TotalLen;
    };
    #endif // DATAHEADER_H
    

    3、DataStream.h:

    • 支持序列化和反序列化操作,
      • 枚举继承char类型意思是说这个枚举里的枚举值底层是用char来存储的
    • 支持是序列化相关的数据类型,
    • 往这个类的写数据和读数据都是提供了两种形式,
    • 流式操作符(<< 或者 >> )和函数(readVal,writeVal)
    // DataStream.h
    #ifndef DATASTREAM_H
    #define DATASTREAM_H
    #include <memory>
    #include <map>
    #include <list>
    #include <vector>
    #include <set>
    #include "DataHeader.h"
    #include "CharVec.h"
    
    class CustomTypeInterface;//前向声明
    class DataStream
    {
    public:
        DataStream(std::unique_ptr<DataHeader> *header = nullptr);
        DataStream(const DataStream &stream);
    
        DataStream& operator =(const DataStream &stream);
    
        enum class DataType : char {
            UnKnown,
            Boolean,
            Char,
            WChar,
            Int,
            UInt,
            Int64,
            Double,
            String,
            WString,
            Vector,
            List,
            Map,
            Set,
            CustomType,
        };
    
        bool operator == (const DataStream &stream) const;
        // 指数组里存放的数据
        int totalSize() const { return m_Header->m_TotalLen; }//数据的总长
        int headerSize() const { return m_Header->m_HeaderLen; }//头部的长度
        int dataSize() const {return m_Header->m_TotalLen - m_Header->m_HeaderLen;}//内容数据的长度
    
        void clear();
    
        // write
        void writeHeader();
        void writeData(const char *data, int len);
        //这里是写入不同数据类型的数据
        DataStream& operator<<(char val);
        void writeVal(char val);
        
        DataStream& operator<<(wchar_t val);
        void writeVal(wchar_t val);
        
        DataStream& operator <<(bool val);
        void writeVal(bool val);
       
        DataStream& operator <<(int val);
        void writeVal(int val);
        
        DataStream& operator <<(unsigned int val);
        void writeVal(unsigned int val);
       
        DataStream& operator <<(int64_t val);
        void writeVal(int64_t val);
       
        DataStream& operator <<(double val);
        void writeVal(double val);
       
        DataStream& operator <<(const std::string &val);
        void writeVal(const std::string &val);
       
        DataStream& operator <<(const std::wstring &val);
        void writeVal(const std::wstring &val);
       
        DataStream& operator <<(CustomTypeInterface *val);
        void writeVal(CustomTypeInterface *val);
       //这里是往不同的STL容器中写入模板类型的数据
        template<typename T>
        DataStream& operator <<(const std::vector<T>& val);
        
        template<typename T>
        void writeVal(const std::vector<T>& val);
      
        template<typename T>
        DataStream& operator <<(const std::list<T>& val);
       
        template<typename T>
        void writeVal(const std::list<T>& val);
        
        template<typename T1, typename T2>
        DataStream& operator <<(const std::map<T1, T2>& val);
    
        template<typename T1, typename T2>
        void writeVal(const std::map<T1, T2>& val);
    
        template<typename T>
        DataStream& operator <<(const std::set<T>& val);
    
        template<typename T>
        void writeVal(const std::set<T>& val);
    
        // read
        void readHeader(const char *data);
        
        template<typename T>
        bool readData(T *val);
        
        bool operator>>(char &val);
        bool readVal(char &val);
        
        bool operator>>(wchar_t& val);
        bool readVal(wchar_t &val);
       
        bool operator>>(bool &val);
        bool readVal(bool &val);
       
        bool operator>>(int &val);
        bool readVal(int &val);
    
        bool operator>>(unsigned int &val);
        bool readVal(unsigned int &val);
        
        bool operator>>(int64_t &val);
        bool readVal(int64_t &val);
       
        bool operator>>(double &val);
        bool readVal(double &val);
        
        bool operator>>(std::string &val);
        bool readVal(std::string &val);
       
        bool operator>>(std::wstring &val);
        bool readVal(std::wstring &val);
        
        bool operator>>(CustomTypeInterface *val);
        bool readVal(CustomTypeInterface *val);
    
        template<typename T>
        bool operator>>(std::vector<T> &val);
    
        template<typename T>
        bool readVal(std::vector<T> &val);
    
        template<typename T>
        bool operator>>(std::list<T> &val);
    
        template<typename T>
        bool readVal(std::list<T> &val);
    
        template<typename T1, typename T2>
        bool operator>>(std::map<T1, T2> &val);
    
        template<typename T1, typename T2>
        bool readVal(std::map<T1, T2> &val);
    
        template<typename T>
        bool operator>>(std::set<T> &val);
    
        template<typename T>
        bool readVal(std::set<T> &val);
    
        // Serialize and Deserialize
        int Serialize(char *buf) const;
        bool Deserialize(const char *buf, int len);
    private:
        std::unique_ptr<DataHeader> m_Header;//存储的客户类型指针
        CharVec  m_DataBuffer;//存储的容器
        int  m_IsFirstWrite;//判断是否为第一次写入
    };
    
    

    4、DataStream.cpp:DataStream.h文件相关函数的实现

    #include "DataStream.h"
    #include "CustomTypeInterface.h"
    
    DataStream::DataStream(std::unique_ptr<DataHeader> *header) :
        m_IsFirstWrite(true)//构造函数的实现
    {
        if (header == nullptr) {//header对象为空指针,重置新的对象指针
            m_Header.reset(new DataHeader);
        }
        else {
            m_Header.reset(header->release());//release()释放关联的原始指针,unique_ptr相关的函数
        }
    }
    
     
    
    DataStream::DataStream(const DataStream &stream)//拷贝构造
    {
        operator =(stream);
    }
    
     
    
    DataStream &DataStream::operator =(const DataStream &stream)//=重载
    {
        if (&stream == this) {//比较对象和原对象相同,没有赋值的必要了
            return *this;
        }
    
        m_Header.reset(new DataHeader);//重载并且初始化
        *m_Header = *stream.m_Header;//相关赋值操作
        m_DataBuffer = stream.m_DataBuffer;
        m_IsFirstWrite = stream.m_IsFirstWrite;
        return *this;
    }
    
    bool DataStream::operator ==(const DataStream &stream) const//==重载
    {
        if (&stream == this) {
            return true;
        }
        if (m_Header.get() == stream.m_Header.get() &&
                m_DataBuffer == stream.m_DataBuffer) {
            return true;
        }
        return false;
    }
    
    void DataStream::clear()
    {
        m_IsFirstWrite = true;
        m_DataBuffer.clear();
        m_Header->reset();
    }
    
     
    
    void DataStream::writeHeader()
    {
        int headerLen = DataHeader::s_HeaderLen;
        writeData((char *)&(m_Header->m_TotalLen), sizeof(int));
        writeData((char *)&headerLen, sizeof(int));
        writeData((char *)&m_Header->m_Id, sizeof(int));
        m_Header->m_HeaderLen = headerLen;
    }
    
     
    
    void DataStream::writeData(const char *data, int len)
    {
        if (len == 0) {
            return ;
        }
        //把他的type写入,
        //如果是第一写入,先把header写入, 然后再写数据,更新totalLen
       if (m_IsFirstWrite) {
            m_IsFirstWrite = false;
            writeHeader();
        }
        //然后在把数据写入
        m_DataBuffer.push(data, len);
        m_Header->m_TotalLen += len;//更新totalLen
        memcpy(m_DataBuffer.begin(), &m_Header->m_TotalLen, sizeof(int));
    }
    
     
    
    void DataStream::writeVal(char val)
    {
        char type = (char)DataType::Char;
        writeData((char *)&(type), sizeof(char));
        writeData(&val, sizeof(char));
    }
    
    void DataStream::writeVal(const std::string &val)
    {
        char type = (char)DataType::String;
        writeData((char *)&(type), sizeof(char));
        int size = val.size();
        writeVal(size);
        writeData(val.c_str(), size);
    }
    
     
    
    void DataStream::writeVal(CustomTypeInterface *val)
    {
        val->serialize(*this, (char)DataType::CustomType);
    }
    
     
    
    void DataStream::readHeader(const char *data)
    {
        int *p = (int *)data;
        m_Header->m_TotalLen  = *p++;
        m_Header->m_HeaderLen = *p++;
        m_Header->m_Id        = *p++;
        m_Header->m_TotalLen -= m_Header->m_HeaderLen;
        m_Header->m_HeaderLen = 0;
    }
    
    //从dataBuffer的数据取出来,然后更新totalLen.
    //由于这个函数是模板函数,所以我们把他放在了头文件。
    /*template<typename T>
    bool DataStream::readData(T *val)
    {
        int size = m_DataBuffer.size();
        int count = sizeof(T);
        if (size < count) {
           return false;
        }
        *val = *((T*)m_DataBuffer.begin());
        m_DataBuffer.removeFromFront(count);
        m_Header->m_TotalLen -= count;
        return true;
    }*/
    //先读取出来类型,然后读取数据
    bool DataStream::readVal(char &val)
    {
        char type = 0;
        if (readData(&type) && type == (char)DataType::Char) {
            return readData(&val);
        }
       return false;
    }
    
     
    
    bool DataStream::readVal(std::string &val)
    {
        char type = 0;
        if (readData(&type) && type == (char)DataType::String) {
            int len = 0;
            if (readVal(len) && len > 0) {
                val.assign(m_DataBuffer.begin(), len);
                m_DataBuffer.removeFromFront(len);
                m_Header->m_TotalLen -= len;
            }
           return true;
        }
        return false;
    }
    
     
    
    bool DataStream::readVal(CustomTypeInterface *val)
    {
        return val->deserialize(*this, (char)DataType::CustomType);
    }
    
     
    
    int DataStream::Serialize(char *buf) const//序列化
    {
        int totalLen = m_Header->m_TotalLen;
        int size = m_DataBuffer.size();
        if (size <= 0 || totalLen == 0 || size != totalLen) {
            return 0;
        }
        memcpy(buf, m_DataBuffer.begin(), totalLen);
        return totalLen;
    }
    
     
    
    bool DataStream::Deserialize(const char *buf, int len)//反序列化
    {
        if (buf == nullptr || len <= 0) {
            return false;
        }
       readHeader(buf);
       m_DataBuffer.clear();
       m_DataBuffer.push(buf + DataHeader::s_HeaderLen, len - DataHeader::s_HeaderLen);
        return true;
    }
    
    

    5、CustomTypeInterface:自定义类型,比如你自己定义了一个结构体,怎样传输它呢。我们为自定义的结构体定义一个接口类。

    class CustomTypeInterface
    {
    public:
        virtual ~CustomTypeInterface() = default;
        virtual void serialize(DataStream &stream, char type) const = 0;
        virtual bool deserialize(DataStream &stream, char type) = 0;
    };
    
    

    测试

    #include <iostream>
    #include "DataStream.h"
    #include "CustomTypeInterface.h"
    
    class Test : public CustomTypeInterface
    {
    public:
        SerializeAndDeserialize(Test, m_A * m_B);
    public:
        int  m_A;
        bool m_B;
    };
    int main(int argc, char *argv[])
    {
        char c1 = 'c';
    
        Test t;
        t.m_A = 1;
        t.m_B = false;
    
        DataStream stream;
        stream.writeVal(c1);
        stream.writeVal(&t);
        int size = stream.totalSize();
        char *data = new char[size];
        stream.Serialize(data); 
    
        DataStream stream2;
        stream2.Deserialize(data, size);
        
        char c2;
        Test t2;
        stream2.readVal(c2);
        stream2.readVal(&t2);
        std::cout << c2 << t2.m_A << t2.m_B;
        return 0;
    
    }
    
    

    在这里插入图片描述
    代码地址:https://download.csdn.net/download/leapmotion/10762437

    参考

    1、 https://www.cnblogs.com/chjxbt/p/11458815.html
    2、 https://www.bbsmax.com/A/kmzLo1jYdG/
    3、https://blog.csdn.net/leapmotion/article/details/83687517

    更多相关内容
  • 序列化和反序列化

    千次阅读 2021-12-11 13:07:52
    以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。 序列化主要有两个用途 把对象的字节序列永久保存到硬盘上,通常存放在一个文件中(序列化对象) 在网络上传送对象的字节序列(网络传输对象)

    我以前确实对序列化,乃至现在也是不是很熟悉,有时候查找资料,但依旧懵懵懂懂,不过还好遇到一个博主,确定写的挺好的,链接会放再底部

    废话不多说,先看官网定义:

    序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

    序列化主要有两个用途

    • 把对象的字节序列永久保存到硬盘上,通常存放在一个文件中(序列化对象)
    • 在网络上传送对象的字节序列(网络传输对象)

    实际上就是将数据持久化,防止一直存储在内存当中,消耗内存资源。而且序列化后也能更好的便于网络运输何传播

    • 序列化:将java对象转换为字节序列
    • 反序列化:把字节序列回复为原先的java对象

    对象如何序列化?

    java目前没有一个关键字是直接定义一个所谓的“可持久化”对象

    对象的持久化和反持久化需要靠程序员在代码里手动显式地进行序列化和反序列化还原的动作。

    举个例子,假如我们要对Student类对象序列化到一个名为student.txt的文本文件中,然后再通过文本文件反序列化成Student类对象:

    按我的理解就是序列化:将一个对象转化为一种格式,能够更好的传输和电脑理解。

    反序列化是转换过来,便于人们观看的。

    先写一个实体类

    import java.io.Serializable;
    public class Student implements Serializable {
        private String name;
        private Integer age;
        private Integer score;
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", score=" + score +
                    '}';
        }
    	//getter、setter省略
    }
    
    

    然后填写一个测试类

    public class test01 {
        public static void serialize(  ) throws IOException {
    
            Student student = new Student();
            student.setName("linko");
            student.setAge( 18 );
            student.setScore( 1000 );
    
            ObjectOutputStream objectOutputStream =
                    new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
            objectOutputStream.writeObject( student );
            objectOutputStream.close();
    
            System.out.println("序列化成功!已经生成student.txt文件");
            System.out.println("==============================================");
        }
    
        public static void deserialize(  ) throws IOException, ClassNotFoundException {
            ObjectInputStream objectInputStream =
                    new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
            Student student = (Student) objectInputStream.readObject();
            objectInputStream.close();
    
            System.out.println("反序列化结果为:");
            System.out.println( student );
        }
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            serialize();
            deserialize();
        }
    }
    

    运行结果:

    image-20211211111125205

    Serializable接口的作用

    这时候我们来看下Serializable接口,这时候我们点进去,会发现这个接口是个空接口,并没有包含任何方法

    image-20211211111237128

    我们会感觉这个接口没什么用,那这时我们不继承Serializable接口,运行一下试试

    image-20211211111501118

    这时候我们会看到这个错误,java.io.NotSerializableException报出了没有序列化异常

    然后我们按照错误提示,由源码一直跟到ObjectOutputStreamwriteObject0()方法底层(虽然我看不懂

    image-20211211112104380

    如果一个对象既不是字符串数组枚举,而且也没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常!

    说的太好了哭,强烈推荐底部链接的那个博主

    他一说我就大概知道懂什么意思了。

    先说说instanceof,它是用来测试一个对象是否为一个类的实例

    // remaining cases
    //判断这个obj对象是否是String类
    if (obj instanceof String) {
        writeString((String) obj, unshared);
        //判断这个obj对象是否是数组
    } else if (cl.isArray()) {
        writeArray(obj, desc, unshared);
        //判断这个obj对象是否是枚举
    } else if (obj instanceof Enum) {
        writeEnum((Enum<?>) obj, desc, unshared);
        //判断这个obj对象是否是实现序列化
    } else if (obj instanceof Serializable) {
        writeOrdinaryObject(obj, desc, unshared);
        //否则就报错
    } else {
        //报错是否由有扩展信息,详细信息可以观看源码
        if (extendedDebugInfo) {
            throw new NotSerializableException(
                cl.getName() + "\n" + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }
    }
    

    所以Serializable接口只是一个做标记用的

    它告诉代码只要是实现了Serializable接口的类都是可以被序列化的!然而真正的序列化动作不需要靠它完成。

    serialVersionUID号有何用?

    在我们写项目的时候,我们会经常看到这么这么一个语句。

    定义一个名为serialVersionUID的字段

    private static final long serialVersionUID = -4392658638228508589L;
    

    为什么有这句话呢,为什么要搞一个名为**serialVersionUID**的序列号

    继续做一个简单实验,依旧是上面的Student类,然后我们先不写序列号。然后在Student中添加一个字段,并添加序列号

    private static final long serialVersionUID = -4392658638228508589L;
    private String name;
    private Integer age;
    private Integer score;
    private Long studentId;
    

    然后再次序列化和反序列化一下

    再然后我们拿刚才已经序列化好的student.txt文件,然后试着反序列化一下。

    test01测试类中,我们将主函数的序列化调用的函数给删掉,再把序列号给删掉

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //serialize();
        deserialize();
    }
    

    我们会发现报错了。太长了,没能截屏出来

    它说序列号不一致

    Exception in thread "main" java.io.InvalidClassException: demo01.Student; local class incompatible: stream classdesc serialVersionUID = -4392658638228508589, local class serialVersionUID = -2825960062149872719
    	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
    	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
    	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
    	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
    	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    	at demo01.test01.deserialize(test01.java:32)
    	at demo01.test01.main(test01.java:41)
    

    从这几个地方我们可以看出几个重要的信息

    • serialVersionUID是序列化前后的唯一标识符
    • 默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!

    第1个问题: serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。

    所以,为了serialVersionUID的确定性,写代码时还是建议,凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID明确值!

    当然,如果不想手动赋值,你也可以借助IDE的自动添加功能,比如我使用的IntelliJ IDEA,按alt + enter就可以为类自动生成和添加serialVersionUID字段,十分方便;

    两种特殊情况

    • 凡是被static修饰的字段是不会被序列化的
    • 凡是被transient修饰符修饰的字段也是不会被序列化的 更正:经过@Programming_artist同学的修正和参考这位作者Java中关键字transient解析,这里不能一概而论。只有实现Serializable接口,被transient修饰符修饰的字段也是不会被序列化的 。后面详细解释一下

    对于第一个,因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域。

    对于第二个,就需要了解transient修饰符的作用了(在实现Serializable接口的情况下

    如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段。

    比如在之前定义的Student类中,加入一个密码字段,但是不希望序列化到txt文本,则可以:

    image-20211211130118972
    jvm是通过Serializable这个标识来实现序列化的,那么我们是否可以自己自定义是否决定序列化呢?答案是可以的,java给我们提供了Externalizable接口,让我们自己实现。
    image-20220425105816374
    从实现的接口上看,它是继承了Serializable接口,但使用这个接口,需要实现writeExternal以及readExternal这两个方法,来自己实现序列化和反序列化。
    实现的过程中,需要自己指定需要序列化的成员变量,此时,static和transient关键词都是不生效的,因为你重写了序列化中的方法。感觉这个static验证也可以再多写点(有时间再更呜
    在这里插入图片描述在这里插入图片描述

    约束性加持(后面两个有时间再更)

    从上面的过程可以看出,序列化和反序列化是有漏洞的,因为序列化和反序列化是有中间过程的,如果别人拿到中间字节流,然后加以伪造或篡改,那反序列化出来的对象就有一定风险了。

    单例模式增强

    分享链接

    展开全文
  • 遇到这个 Java Serializable 序列化这个接口,我们可能会有如下的问题a,什么叫序列化和反序列化 b,作用。为啥要实现这个 Serializable 接口,也就是为啥要序列化 c,serialVersionUID 这个的值到底是在怎么设置的...

    遇到这个 Java Serializable 序列化这个接口,我们可能会有如下的问题
    a,什么叫序列化和反序列化
    b,作用。为啥要实现这个 Serializable 接口,也就是为啥要序列化
    c,serialVersionUID 这个的值到底是在怎么设置的,有什么用。有的是1L,有的是一长串数字,迷惑ing。

    我刚刚见到这个关键字 Serializable 的时候,就有如上的这么些问题。

    在处理这个问题之前,你要先知道一个问题,这个比较重要。
    这个Serializable接口,以及相关的东西,全部都在 Java io 里面的。
     

    1,序列化和反序列化的概念

    序列化:把对象转换为字节序列的过程称为对象的序列化。
    反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

    上面是专业的解释,现在来点通俗的解释。在代码运行的时候,我们可以看到很多的对象(debug过的都造吧),
    可以是一个,也可以是一类对象的集合,很多的对象数据,这些数据中,
    有些信息我们想让他持久的保存起来,那么这个就叫序列化。
    就是把内存里面的这些对象给变成一连串的字节(bytes)描述的过程。
    常见的就是变成文件
    我不序列化也可以保存文件啥的呀,有什么影响呢?我也是这么问的。

    2,什么情况下需要序列化 

    当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
    当你想用套接字在网络上传送对象的时候;
    当你想通过RMI传输对象的时候;

    (老实说,上面的几种,我可能就用过个存数据库的。)

     

    3,java如何实现序列化

    实现Serializable接口即可

    上面这些理论都比较简单,下面实际代码看看这个序列化到底能干啥,以及会产生的bug问题。

    先上对象代码,飞猪.java

    package com.lxk.model;
    
    import java.io.Serializable;
    
    /**
     * @author lxk on 2017/11/1
     */
    public class FlyPig implements Serializable {
        //private static final long serialVersionUID = 1L;
        private static String AGE = "269";
        private String name;
        private String color;
        transient private String car;
    
        //private String addTip;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getCar() {
            return car;
        }
    
        public void setCar(String car) {
            this.car = car;
        }
    
        //public String getAddTip() {
        //    return addTip;
        //}
        //
        //public void setAddTip(String addTip) {
        //    this.addTip = addTip;
        //}
    
        @Override
        public String toString() {
            return "FlyPig{" +
                    "name='" + name + '\'' +
                    ", color='" + color + '\'' +
                    ", car='" + car + '\'' +
                    ", AGE='" + AGE + '\'' +
                    //", addTip='" + addTip + '\'' +
                    '}';
        }
    }
    

    注意下,注释的代码,是一会儿要各种情况下使用的。

    下面就是main方法啦

    package com.lxk.test;
    
    import com.lxk.model.FlyPig;
    
    import java.io.*;
    
    /**
     * 序列化测试
     *
     * @author lxk on 2017/11/1
     */
    public class SerializableTest {
        public static void main(String[] args) throws Exception {
            serializeFlyPig();
            FlyPig flyPig = deserializeFlyPig();
            System.out.println(flyPig.toString());
    
        }
    
        /**
         * 序列化
         */
        private static void serializeFlyPig() throws IOException {
            FlyPig flyPig = new FlyPig();
            flyPig.setColor("black");
            flyPig.setName("naruto");
            flyPig.setCar("0000");
            // ObjectOutputStream 对象输出流,将 flyPig 对象存储到E盘的 flyPig.txt 文件中,完成对 flyPig 对象的序列化操作
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/flyPig.txt")));
            oos.writeObject(flyPig);
            System.out.println("FlyPig 对象序列化成功!");
            oos.close();
        }
    
        /**
         * 反序列化
         */
        private static FlyPig deserializeFlyPig() throws Exception {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/flyPig.txt")));
            FlyPig person = (FlyPig) ois.readObject();
            System.out.println("FlyPig 对象反序列化成功!");
            return person;
        }
    }
    

    对上面的2个操作文件流的类的简单说明

    ObjectOutputStream代表对象输出流:

    它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

    ObjectInputStream代表对象输入流:

    它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

    具体怎么看运行情况。

    第一种:上来就这些代码,不动,直接run,看效果。

    实际运行结果,他会在 d:/flyPig.txt 生成个文件。

    从运行结果上看:

    1,他实现了对象的序列化和反序列化。

    2,transient 修饰的属性,是不会被序列化的。我设置的奥迪四个圈的车不见啦,成了null。my god。

    3,你先别着急说,这个静态变量AGE也被序列化啦。这个得另测。

     

    第二种:为了验证这个静态的属性能不能被序列化和反序列化,可如下操作。

        public static void main(String[] args) throws Exception {
            serializeFlyPig();
            //FlyPig flyPig = deserializeFlyPig();
            //System.out.println(flyPig.toString());
        }

    这个完了之后,意思也就是说,你先序列化个对象到文件了。这个对象是带静态变量的static。

    现在修改flyPig类里面的AGE的值,给改成26吧。

    然后,看下图里面的运行代码和执行结果。

    可以看到,刚刚序列化的269,没有读出来。而是刚刚修改的26,如果可以的话,应该是覆盖这个26,是269才对。

    所以,得出结论,这个静态static的属性,他不序列化。

     

    第三种:示范这个 serialVersionUID 的作用和用法

    最暴力的改法,直接把model的类实现的这个接口去掉。然后执行后面的序列化和反序列化的方法。直接报错。

    抛异常:NotSerializableException

    这个太暴力啦,不推荐这么干。

    然后就是,还和上面的操作差不多,先是单独执行序列化方法。生成文件。
    然后,打开属性 addTip ,这之后,再次执行反序列化方法,看现象。

    抛异常:InvalidClassException  详情如下。

    InvalidClassException: com.lxk.model.FlyPig; 
    local class incompatible: 
    stream classdesc serialVersionUID = -3983502914954951240, 
    local class serialVersionUID = 7565838717623951575

    解释一下:

    因为我再model里面是没有明确的给这个 serialVersionUID 赋值,但是,Java会自动的给我赋值的,

    这个值跟这个model的属性相关计算出来的。

    我保存的时候,也就是我序列化的时候,那时候还没有这个addTip属性呢,

    所以,自动生成的serialVersionUID 这个值,

    在我反序列化的时候Java自动生成的这个serialVersionUID值是不同的,他就抛异常啦。

    (你还可以反过来,带ID去序列化,然后,没ID去反序列化。也是同样的问题。)

    再来一次,就是先序列化,这个时候,把 private static final long serialVersionUID = 1L; 这行代码的注释打开。那个addTip属性先注释掉,序列化之后,再把这个属性打开,再反序列化。看看什么情况。

    这个时候,代码执行OK,一切正常。good。序列化的时候,是没的那个属性的,在发序列化的时候,对应的model多了个属性,但是,反序列化执行OK,没出异常。

     

    这个现象对我们有什么意义:

    老铁,这个意义比较大,首先,你要是不知道这个序列化是干啥的,万一他真的如开头所讲的那样存数据库(这个存db是否涉及到Java的序列化估计还的看什么数据库吧)啦,socket传输啦,rmi传输啦。虽然我也不知道这是干啥的。你就给model bean 实现了个这个接口,你没写这个 serialVersionUID 那么在后来扩展的时候,可能就会出现不认识旧数据的bug,那不就炸啦吗。回忆一下上面的这个出错情况。想想都可怕,这个锅谁来背? 

    所以,有这么个理论,就是在实现这个Serializable 接口的时候,一定要给这个 serialVersionUID 赋值,就是这么个问题。

    这也就解释了,我们刚刚开始编码的时候,实现了这个接口之后,为啥eclipse编辑器要黄色警告,需要添加个这个ID的值。而且还是一长串你都不知道怎么来的数字。

     

    下面解释这个 serialVersionUID 的值到底怎么设置才OK

    首先,你可以不用自己去赋值,Java会给你赋值,但是,这个就会出现上面的bug,很不安全,所以,还得自己手动的来。

    那么,我该怎么赋值,eclipse可能会自动给你赋值个一长串数字。这个是没必要的。

    可以简单的赋值个 1L,这就可以啦。。这样可以确保代码一致时反序列化成功。

    不同的serialVersionUID的值,会影响到反序列化,也就是数据的读取,你写1L,注意L大些。计算机是不区分大小写的,但是,作为观众的我们,是要区分1和L的l,所以说,这个值,闲的没事不要乱动,不然一个版本升级,旧数据就不兼容了,你还不知道问题在哪。。。

     

    下面是摘自 jdk api 文档里面关于接口 Serializable 的描述
    类通过实现 java.io.Serializable 接口以启用其序列化功能。
    未实现此接口的类将无法使其任何状态序列化或反序列化。
    可序列化类的所有子类型本身都是可序列化的。因为实现接口也是间接的等同于继承。
    序列化接口没有方法或字段,仅用于标识可序列化的语义。

    关于 serialVersionUID 的描述

    https://blog.csdn.net/qq_27093465?viewmode=contents

    (注意对比一下,这个截图的两段话,就是对应下面的2段中文。仔细看这2段话,就能解释43楼的问题,静态属性不会被序列化,但是却又有一个特殊的静态属性,会被序列化,没办法,这个静态属性是亲生的。自带的。)

    序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:

    如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。

     

    最后更新一下

    1,(针对25楼的留言:序列化的类的所有成员变量是不是都要是基本类型或实现Serializable接口的类型?)

    当属性是对象的时候,如果这个对象,没实现序列化接口,那么上面的方法在序列化的时候就在执行oos.writeObject(flyPig)时候,报错了“Exception in thread "main" java.io.NotSerializableException: com.lxk.model.Bird”。然后给刚刚的属性的对象加上实现序列化的接口之后,上面的测试就正常通过了。你这个问题问的好。

    结论:要实现序列化的对象,所有涉及的引用,都需要实现序列化接口才可以。

    2,(38楼问,这个serialVersionUID的值在存数据库的时候,存哪里了?)

    大师兄

    大师兄

    好问题,答不上来呀!!!我也是郁闷了,那大概猜一下,数据库没有使用Java这一套序列化,而是不同db各自实现了一套自己的序列化,so,就跟Java的这个静态属性没关系啦。静态属性是不存db的。然后,你就发现,你即使没实现这个Java的序列化的接口,也可以正常的存db,取db。太机智了,这个解释,满分,为啥呢,这个serialVersionUID是Java给你提供的一个序列化和反序列化用的,你要是不使Java这一套的话,那就不需要考虑这个问题啦呀。在上面的测试代码里面也是使用这些个API去序列化以及反序列化的。???

    3,(43楼问的问题:既然要比较新旧serialVersionUID, 旧的serialVersionUID是不是也应该序列化到文件中, 但serialVersionUID是static类型的, 是不会被序列化到文件中的)

    这个serialVersionUID是jdk亲生的,你写或者不写,只要实现了接口,他就是存在的,在序列化的时候,他还真就序列化,存起来了。不然在反序列化的时候,就没的对比了嘛,因为他是亲生的,所以,咱手动添加的静态,和这个静态是不能比的。

     

    我写完文章,给自己点个赞,不过分吧,
    不过分,那我可就点啦啊。
    我先点为敬,你们随意。大家随意。不要客气。。。

    展开全文
  • 序列化和反序列化的底层实现原理是什么?

    万次阅读 多人点赞 2018-04-07 13:53:41
    序列化和反序列化作为Java里一个较为基础的知识点,大家心里也有那么几句要说的,但我相信很多小伙伴掌握的也就是那么几句而已,如果再深究问一下Java如何实现序列化和反序列化的,就可能不知所措了!遥记当年也被问...

    序列化和反序列化作为Java里一个较为基础的知识点,大家心里也有那么几句要说的,但我相信很多小伙伴掌握的也就是那么几句而已,如果再深究问一下Java如何实现序列化和反序列化的,就可能不知所措了!遥记当年也被问了这一个问题,自信满满的说了一大堆,什么是序列化、什么是反序列化、什么场景的时候才会用到等,然后面试官说:那你能说一下序列化和反序列化底层是如何实现的吗?一脸懵逼,然后回家等通知!

    一、基本概念

    1、什么是序列化和反序列化

    (1)Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程;

    (2)**序列化:**对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

    (3)**反序列化:**客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

    (4)本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

    2、为什么需要序列化与反序列化

    我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。

    那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的!如何做到呢?这就需要Java序列化与反序列化了!

    换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

    当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

    总的来说可以归结为以下几点:

    (1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中;
    (2)通过序列化以字节流的形式使对象在网络中进行传递和接收;
    (3)通过序列化在进程间传递对象;

    3、序列化算法一般会按步骤做如下事情:

    (1)将对象实例相关的类元数据输出。
    (2)递归地输出类的超类描述直到不再有超类。
    (3)类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
    (4)从上至下递归输出实例的数据

    二、Java如何实现序列化和反序列化

    1、JDK类库中序列化和反序列化API

    (1)java.io.ObjectOutputStream:表示对象输出流;

    它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;

    (2)java.io.ObjectInputStream:表示对象输入流;

    它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;

    2、实现序列化的要求

    只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!

    3、实现Java对象序列化与反序列化的方法

    假定一个User类,它的对象需要序列化,可以有如下三种方法:

    (1)若User类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化

    ObjectOutputStream采用默认的序列化方式,对User对象的非transient的实例变量进行序列化。
    ObjcetInputStream采用默认的反序列化方式,对对User对象的非transient的实例变量进行反序列化。

    (2)若User类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。

    ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化。
    ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。

    (3)若User类实现了Externalnalizable接口,且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

    ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。
    ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。

    4、JDK类库中序列化的步骤

    步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:

    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\object.out"));
    

    步骤二:通过对象输出流的writeObject()方法写对象:

    oos.writeObject(new User("xuliugen", "123456", "male"));
    

    5、JDK类库中反序列化的步骤

    步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:

    ObjectInputStream ois= new ObjectInputStream(new FileInputStream("object.out"));
    

    步骤二:通过对象输出流的readObject()方法读取对象:

    User user = (User) ois.readObject();
    

    说明:为了正确读取数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。

    6、序列化和反序列化的示例

    为了更好地理解Java序列化与反序列化,举一个简单的示例如下:

    public class SerialDemo {
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
    	    //序列化
            FileOutputStream fos = new FileOutputStream("object.out");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            User user1 = new User("xuliugen", "123456", "male");
            oos.writeObject(user1);
            oos.flush();
            oos.close();
    		//反序列化
            FileInputStream fis = new FileInputStream("object.out");
            ObjectInputStream ois = new ObjectInputStream(fis);
            User user2 = (User) ois.readObject();
            System.out.println(user2.getUserName()+ " " + 
    	        user2.getPassword() + " " + user2.getSex());
            //反序列化的输出结果为:xuliugen 123456 male
        }
    }
    
    public class User implements Serializable {
        private String userName;
        private String password;
        private String sex;
        //全参构造方法、get和set方法省略
    }
    
    

    object.out文件如下(使用UltraEdit打开):

    这里写图片描述

    注:上图中0000000h-000000c0h表示行号;0-f表示列;行后面的文字表示对这行16进制的解释;对上述字节码所表述的内容感兴趣的可以对照相关的资料,查阅一下每一个字符代表的含义,这里不在探讨!

    类似于我们Java代码编译之后的.class文件,每一个字符都代表一定的含义。序列化和反序列化的过程就是生成和解析上述字符的过程!

    序列化图示:

    这里写图片描述

    反序列化图示:

    这里写图片描述

    三、相关注意事项

    1、序列化时,只对对象的状态进行保存,而不管对象的方法;

    2、当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

    3、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

    4、并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

    • 安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;

    • 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;

    5、声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

    6、序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途:

    • 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

    • 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

    7、Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的;

    8、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;

    四、总结

    看到这里,可能已经让我们很满足了,毕竟已经知道了我们平时使用的序列化和反序列化是如何进行操作的,Java给我们提供了哪些接口可供使用,也比我们最初知道的简单的什么是序列化、反序列化以及作用多了很多!后续内容我们也会不断在讨论和更新!


    参考文章:

    1、https://zhidao.baidu.com/question/688891250408618484.html
    2、https://blog.csdn.net/morethinkmoretry/article/details/5929345
    3、https://www.jianshu.com/p/edcf7bd2c085
    4、https://blog.csdn.net/xiaocaidexuexibiji/article/details/22692097

    在这里插入图片描述

    【视频福利】2T免费学习视频,搜索或扫描上述二维码关注微信公众号:Java后端技术(ID: JavaITWork)回复:1024,即可免费获取!内含SSM、Spring全家桶、微服务、MySQL、MyCat、集群、分布式、中间件、Linux、网络、多线程,Jenkins、Nexus、Docker、ELK等等免费学习视频,持续更新!

    展开全文
  • SpringBoot的序列化和反序列化

    千次阅读 多人点赞 2020-11-11 15:06:16
    2、为什么要实现对象的序列化和反序列化? (1)我们创建的Java对象被存储在Java堆中,当程序运行结束后,这些对象会被JVM回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据...
  • 序列化和反序列化的区别

    千次阅读 2022-03-15 10:19:52
    首先我们来了解为什么需要序列化 卖个关子,大家玩游戏的时候,相信都知道【存档】的功能吧,每次我们重新进入游戏的时候,直接载入存档就可以了,不用重新在重头开始,这样我们的游戏也不会丢失 从面向对象的思维...
  • 序列化和反序列化是Java中最基础的知识点,也是很容易被大家遗忘的,虽然天天使用它,但并不一定都能清楚的说明白。我相信很多小伙伴们掌握的也就几句概念、关键字(Serializable)而已,如果深究问一下序列化和反序列...
  • MapReduce自定义序列化和反序列化(带案例分析案例解决代码)
  • 序列化和反序列化的详解

    万次阅读 多人点赞 2018-09-19 09:09:29
    1、序列化和反序列化的定义:  (1)Java序列化就是指把Java对象转换为字节序列的过程  Java反序列化就是指把字节序列恢复为Java对象的过程。  (2)序列化最重要的作用:在传递保存对象时.保证对象的完整性和可...
  • C++实现二叉树的序列化和反序列化

    千次阅读 2019-07-06 10:45:45
    二叉树的序列化和反序列化 2. 思路 2.1 序列化 首先我们介绍二叉树先序序列化的方式,假设序列化的结果字符串为str,初始时str等于空字符串。先序遍历二叉树,如果遇到空节点,就在str的末尾加上“#!”,“#”表示...
  • JSON序列化和反序列化

    千次阅读 2021-01-24 15:49:17
    1、什么是json序列化和反序列化? json序列化:就是JavaBean对象转化为JSON格式的字符串。 反序列化:就是序列化方向,将字符串转化为JavaBean。 2、为什么要序列化和反序列化? 现在比较流行前后端分离的项目,...
  • JSON对象定义基本使用 [JSON对象字符串对象的互转] JSON数据Java对象的相互转换 Java对象转为JSON数据 JSON数据转Java对象 Demo JSON概述 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式...
  • c#中的序列化和反序列化

    千次阅读 2019-08-13 21:25:48
    第一步:什么是序列化和反序列化 序列化定义:将对象转化为容易传输的格式的过程。 反序列化定义:重新解析构造被序列化的对象 第二步:为什么要序列化和反序列化 当两个进程在进行远程通信时,彼此可以发送各种...
  • 面试题 - Java序列化和反序列化

    千次阅读 2020-09-15 18:03:22
    将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ObjectInputStream ...
  • 详解C# 序列化和反序列化

    千次阅读 2019-11-04 21:50:01
    今天我利用这篇文章给大家讲解一下 C# 中的序列化反序列化。这两个概念我们在开发中经常用到,但是我们绝大部分...在所有的开发语言中都存在序列化和反序列化这个概念,所谓的序列化就是把一个对象信息转化为一个...
  • Qt序列化和反序列化

    千次阅读 2019-05-15 15:10:42
    反序列化 序列化: Qt中实现对象序列化的类是QDataStream,写二进制到流中 QFile file("file.dat"); //创建一个文档 file.open(QIODevice::WriteOnly);//打开并只写 QDataStream out(&file); //序列...
  • 成为对象的反序列化 序列化的高阶认识 简单认识一下 Java 原生序列化 前面的代码中演示了,如何通过JDK提供了Java对象的序列化方式实现对象序列化传输,主 要通过输出流java.io.ObjectOutputStream对象输入流java....
  • 什么是Java序列化和反序列化

    千次阅读 2020-11-21 11:11:46
    一、先来说说什么是序列化和反序列化? java对象序列化的意思就是将对象的状态转换成字节流,以后可以通过这些值在生成相同状态的对象,对象序列化就是对象持久化的一种实现方法,它是将对象的属性方法转化为一...
  • kafka系列之序列化和反序列化

    千次阅读 2019-08-16 20:03:28
    文章目录简介基本原理分析自定义序列化组件测试 ...long,String等,这归功于kafka的序列化和反序列化机制。 基本原理分析 在之前的一篇文章springboot集成kafka示例中,我使用的是kafka原生的Str...
  • Unity json 序列化 反序列化

    千次阅读 2021-11-30 14:13:26
    // Json 反序列化(读取) public static PlayerList Load() { var jsonContent = PlayerPrefs.GetString("PlayerListData"); //Debug.Log(jsonContent); return jsonContent.IsNullOrEmpty() ? new PlayerList() :...
  • java序列化和反序列化,面试必备

    千次阅读 多人点赞 2020-04-26 17:04:19
    一、序列化反序列化、使用场景、意义。 序列化:将对象写入IO流中; 反序列化:从IO流中恢复对象; 意义:序列化机制允许将实现序列化的Java对象转换为字节序列,并将字节序列保存在磁盘中,或通过网络传输,以...
  • 题目 序列化是将数据结构或对象转换为一系列位的过程 以便它可以存储在文件或内存缓冲区中 或通过网络连接链路传输 以便稍后在同一个或另一个计算机环境中重建
  • c语言序列化和反序列化

    千次阅读 2019-05-03 20:15:08
    这里写自定义目录标题c语言序列化和反序列化tplut.htplut.c测试代码参考 c语言序列化和反序列化 网络调用,数据传输都需要把数据序列化和反序列化。杀鸡不喜欢用牛刀,自己从底层设计协议又太繁琐,难以维护扩展...
  • 什么是序列化和反序列化

    千次阅读 2021-02-03 10:04:57
    1、序列化和反序列化的定义: (1)Java序列化就是指把Java对象转换为字节序列的过程 Java反序列化就是指把字节序列恢复为Java对象的过程。 (2)序列化最重要的作用:在传递保存对象时.保证对象的完整性和可传递性...
  • 1、什么是序列化和反序列化 (1)Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程; (2)**序列化:**对象序列化的最主要的用处就是在传递保存对象的时候,...
  • 序列化和反序列化 —— unity篇

    千次阅读 2022-01-19 10:07:21
    深入浅出理解序列化 —— unity篇
  • java序列化和反序列化以及序列化ID的作用分析

    万次阅读 多人点赞 2017-08-07 20:26:07
    java序列化和反序列化以及序列化ID的作用分析

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,324,967
精华内容 529,986
关键字:

序列化和反序列化