• 序列化原理机制新谈


    什么是序列化 

    java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以读取字节流中的信息来重构一个相同的对象。序列化机制在java中有着广泛的应用,EJB、RMI、hessian等技术都是以此为基础的。 

    so,序列化一般用于以下场景: 

    1:永久性保存对象,保存对象的字节序列到本地文件或者数据库中

    2:通过序列化以字节流的形式使对象在网络中进行传递和接收;

    3:通过序列化在进程间传递对象。 

    接下来我们将从序列化机制原理等方面进行剖析

    如何序列化一个对象

    类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

      1:我们先来看看一个例子将对象序列化为一个字节流

     

    我们将对象序列化并输出。ObjectOutputStream能把Object输出成Byte流。---上图显示的20行-26行

    我们将Byte流暂时存储到temp.out文件里。

    而在33行-39行,我们利用反序列化,根据字节流重建对象。

     从上面代码中,我们不难看出,序列化和反序列化的俩个主要类:ObjectOutputStream、ObjectInputStream。

     序列化后的内容

    通过上面的例子我们知道,对象被序列化存在至文件temp.out中,我们可以通过工具UltraEdit以16进制方式打开该文件,看看它里面是以什么样的形式存在组织我们的对象的。

     

    每一行分号后面的内容都是前面16进制码的注释。

    对于这些我们先随意YY下,这些内容的组织方式应该是:

    1:对象类型描述

    2:对象属性类型描述

    3:对象属性值

     现在我们将以一个全面的例子来说明该问题:代码-1.1

    class parent implements Serializable {  
        int parentVersion = 10;  
    }  
    class contain implements Serializable {  
        int containVersion = 11;  
    }  
    public class SerialTest extends parent implements Serializable {  
        int     version = 66;  
        contain con     = new contain();  
        public int getVersion() {  
            return version;  
        }  
        public static void main(String args[]) throws IOException {  
            FileOutputStream fos = new FileOutputStream("temp_1.out");  
            ObjectOutputStream oos = new ObjectOutputStream(fos);  
            SerialTest st = new SerialTest();  
            oos.writeObject(st);  
            oos.flush();  
            oos.close();  
        }  
    } 

    temp_1.out文件内容如下:

     

    先对上面图简单注释下:0000000h-000000c0h表示行号(我们一次可以称为第一行,第二行…………),0~f表示列;行后面的文字表示对这行16进制的解释。

     我们来仔细看看这些字节都是些说明东西。第一行的:0列-4列

    1.AC ED: STREAM_MAGIC. 声明使用了序列化协议. 可以理解为实现了Serializable类

    2.00 05: STREAM_VERSION. 序列化协议版本.

    3.0x73: TC_OBJECT. 声明这是一个新的对象. 

    因此第一步存储的就是序列化的描述。

    二:输出SerialTest类的描述:第一行的5列到三行的7列

    1:4.0x72: TC_CLASSDESC. 声明这里开始一个新Class。

    2. 00 15: Class名字的长度(包括package名称:如本例就是:testSerial.SerialTest;正好21的长度,16进制表示就为15了).

    3. 74 65 73 74 53 65 72 69  61 6C 2E 53 65 72 69 61 6c 54 65 73 74 表示testSerial/SerialTest类的全限定名

    4. 第二行的D列到3行的4列表示: SerialVersionUID, 序列化ID,如果没有指定,

    则会由算法随机生成一个8byte的ID.

    5. 0x02: 标记号. 该值声明该对象支持序列化。

    6. 00 02: 该类所包含的域个数。

    三: 接下来,输出其中的一个域(一次从上到下父类到子类),int version=66;3行8列-4行1列

    1. 0x49: 域类型. 49 代表"I", 也就是Int.

    2. 00 07: 域名字的长度.

    3. 76 65 72 73 69 6F 6E: version,域名字描述.

    四:另外一个域,contain con = new contain();这个有点特殊,是个对象。?描述对象类型引用时需要使用JVM的标准对象签名表示法,4行2列-5行f列

    1:0x4C: 域的类型. 2:00 03: 域名字长度. 3:63 6F 6E: 域名字描述,con 4:0x74: TC_STRING. 代表一个new String.用String来引用对象。 5:00 14: 该String长度.既:LtestSerial/contain的长度

    6:4行b-5行e列: LtestSerial/contain;, JVM的标准对象签名表示法. 6:0x78: TC_ENDBLOCKDATA,对象数据块结束的标志

    五:Parent类描述了 6行的0列到7行的e列

    1:0x72: TC_CLASSDESC. 声明这个是个新类. 2:00 11: 类名长度. 3:6行3列-7行3列: testSerial.parent,类名描述。 4:7行4列-7行b列: SerialVersionUID, 序列化ID. 5:0x02: 标记号. 该值声明该对象支持序列化. 6:00 01: 类中域的个数.

    六:parent类的域描述,int parentVersion=100 7行f列-9行0列

    1:0x49: 域类型. 49 代表"I", 也就是Int. 2:00 0D: 域名字长度. 3:8行2列-8行e列: parentVersion,域名字描述。 4:0x78: TC_ENDBLOCKDATA,对象块结束的标志。 5:0x70: TC_NULL, 说明没有其他超类的标志。

    七:到此为止,算法已经对所有的类的描述都做了输出。下一步就是把实例对象的实际值输出了。这时候是从parent Class的域开始的,9行一列-9行4列

    00 00 00 0A: 10, parentVersion域的值.

    八:还有SerialTest类的域:9行5列-9行8列

    00 00 00 42: 66, version域的值.

    九:再往后的bytes比较有意思,算法需要描述contain类的信息

    1: 0x73: TC_OBJECT, 声明这是一个新的对象.

    2:0x72: TC_CLASSDESC声明这里开始一个新Class.

    3:00 12: 类名的长度.

    4:9行d列-10行e列:TestSerial.contain,类名描述.

    5:10行f列-11行6列: SerialVersionUID, 序列化ID.

    6: 0x02: Various flags. 标记号. 该值声明该对象支持序列化

    7: 00 01: 类内的域个数。

    十:.输出contain的唯一的域描述,int containVersion=11

    1:0x49: 域类型. 49 代表"I", 也就是Int..

    2:00 0E: 域名字长度.

    3:63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, 域名字描述.

    4:0x78: TC_ENDBLOCKDATA对象块结束的标志.

    十一:这时,序列化算法会检查contain是否有超类,如果有的话会接着输出。     

       0x70:TC_NULL,没有超类了。

     

    十二:最后,将contain类实际域值输出。        

       00 00 00 0B: 11, containVersion的值.

    总结下序列化后二进制流中按哪些顺序存储哪些内容:

    ◆当前类描述  

    ◆当前类属性描述 

     ◆超类描述

     ◆超类属性描述(如果超类还有超类,则依次递归)

     ◆超类属性值描述

     ◆ 子类属性值描述

     既是:类描述是从下到上,类属性描述是从上到下。俩个是反着来的。

    transient和static的类变量的序列化

    我们现在回过去看看代码1.1。然后修成下:如下---代码1.2

    省略前面:  
      
    public class SerialTest extends parent implements Serializable {  
      
        int                  version       = 66;  
        public static int    testStatic    = 1;  
        public transient int testTransient = 2;  
      
    public int getVersion() {  
      
            return version;  
      
        }  
                 …………………………………………  
    } 

     类SerialTest 新增了俩个变量

      public static int    testStatic    = 1;

     public transient int testTransient = 2;

    然后再看看重新生成的temp_1.out文件内容,我们惊奇发现文件内容和没有新增变量之前是一模一样的。

    so,我们推理static和transient变量被序列化的时候,并不会序列化。现在哥们我用别人的总结来总结自己的

           序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员

         那对于这些问题,我们该如何进行序列化和反序列化呢?

    简单,也就是说我们要对这俩个类型的变量单独处理,怎么办?就是在出现这类变量的所属类中增加俩个方法

    private void writeObject(java.io.ObjectOutputStream out) throws IOException  
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;  

    而对应于我们的类中添加的方法就是

    public class SerialTest extends parent implements Serializable {  
        //省略  
        private void writeObject(ObjectOutputStream out) throws IOException {  
            out.defaultWriteObject();  
            out.writeInt(this.testStatic);  
            out.writeInt(this.testTransient);  
        }  
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
            in.defaultReadObject();  
            this.testStatic = in.readInt();  
            this.testTransient = in.readInt();  
        }  
          
    }

    当ObjectOutputStream对一个SerialTest对象进行序列化时,如果该对象具有writeObject()方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的writeObjectt()方法中,可以先调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是defaultReadObject()方法。

       ObjectOutputStream.defaultWriteObject() :将当前类的非静态(static)和非瞬态字段(transient)写入此流。

       ObjectInputStream.defaultReadObject() :   从此流读取当前类的非静态和非瞬态字段。

    Externalizable的作用

    对于实现Serializable的类来说,在序列化的时候,所有的非静态(static)和非瞬态字段(transient)会被自动序列化,如果有一些特殊要求,我们可以完全手动控制哪些字段要被序列化,哪些不要序列化。将他们的生死大权完全掌握在咱手中。怎么办?这个时候就应该谈谈Externalizable类了。

    只要实现Externalizable这个类,并且复写

    readExternal(ObjectInput in) throws IOException,CalssNotFoundException  
    writeExternal(ObjectOutput out) throws IOException,CalssNotFoundException  

    就可以了。

    readExternal(ObjectInput in) throws IOException,CalssNotFoundException方法中,可以自行决定从in读取哪些对象数据。

     writeExternal(ObjectOutput out) throws IOException,CalssNotFoundException方法中,可以自行决定将什么数据write到out去。

    这俩个方法分别会在在ObjectOutputStream.writeObject(object);ObjectInputStream.readObject()自动执行。

    引用:

    序列化原理机制浅谈 - CSDN博客
    http://blog.csdn.net/morethinkmoretry/article/details/5929345

    个人:

    材料:

    import org.jboss.marshalling.MarshallerFactory;
    import org.jboss.marshalling.Marshalling;
    import org.jboss.marshalling.MarshallingConfiguration;

    为范本

    cause:

    netty传输

    总结:

    1、序列化两个目的:1、性能 2 传输

    2、序列化原理:内容》变为字节流

    3、序列化结果:传输(netty传输)或者存储

    补充:上文中的

    TC_BASE

    TC_REFERENCE

    为类描述,源码见:java.io.ObjectStreamConstants

  • 相关阅读:
    《java入门第一季》之面向对象(static关键字)
    《java入门第一季》之面向对象(面向对象案例详解)
    《java入门第一季》之面向对象面试题(面向对象都做了哪些事情)
    《java入门第一季》之面向对象(成员方法)
    《android入门第一季》之android目录结构详解
    Vue 中的 Props 与 Data 细微差别,你知道吗?
    使用Vue 3.0做JSX(TSX)风格的组件开发
    vue中Axios的封装和API接口的管理
    在 Vue.js 中制作自定义选择组件
    webpack打包原理
  • 原文地址:https://www.cnblogs.com/stevenlii/p/8516064.html
Copyright © 2020-2023  润新知