zoukankan      html  css  js  c++  java
  • Java对象表示方式1:序列化、反序列化的作用

    1.序列化是的作用和用途

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

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

    对象的序列化主要有两种用途:
      1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
      2) 在网络上传送对象的字节序列。

    2.序列化的步骤

           java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
      java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
      只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
      对象序列化包括如下步骤:
      1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
      2) 通过对象输出流的writeObject()方法写对象。

      对象反序列化的步骤如下:
      1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
      2) 通过对象输入流的readObject()方法读取对象。

    3.默认的序列化

    序列化只需要实现java.io.Serializable接口就可以了。序列化的时候有一个serialVersionUID参数,Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化,Java虚拟机会把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的实体类,可以进行反序列化,否则Java虚拟机会拒绝对这个实体类进行反序列化并抛出异常。serialVersionUID有两种生成方式:

    1、默认的1L

    2、根据类名、接口名、成员方法以及属性等来生成一个64位的Hash字段

    如果实现java.io.Serializable接口的实体类没有显式定义一个名为serialVersionUID、类型为long的变量时,Java序列化机制会根据编译的.class文件自动生成一个serialVersionUID,如果.class文件没有变化,那么就算编译再多次,serialVersionUID也不会变化。换言之,Java为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法看一个例子:

    复制代码
     1 public class SerializableObject implements Serializable
     2 {
     3     private static final long serialVersionUID = 1L;
     4     
     5     private String str0;
     6     private transient String str1;
     7     private static String str2 = "abc";
     8     
     9     public SerializableObject(String str0, String str1)
    10     {
    11         this.str0 = str0;
    12         this.str1 = str1;
    13     }
    14     
    15     public String getStr0()
    16     {
    17         return str0;
    18     }
    19 
    20     public String getStr1()
    21     {
    22         return str1;
    23     }
    24 }
    复制代码
    复制代码
     1 public static void main(String[] args) throws Exception
     2 {
     3     File file = new File("D:" + File.separator + "s.txt");
     4     OutputStream os = new FileOutputStream(file);  
     5     ObjectOutputStream oos = new ObjectOutputStream(os);
     6     oos.writeObject(new SerializableObject("str0", "str1"));
     7     oos.close();
     8         
     9     InputStream is = new FileInputStream(file);
    10     ObjectInputStream ois = new ObjectInputStream(is);
    11     SerializableObject so = (SerializableObject)ois.readObject();
    12     System.out.println("str0 = " + so.getStr0());
    13     System.out.println("str1 = " + so.getStr1());
    14     ois.close();
    15 }
    复制代码

    先不运行,用一个二进制查看器查看一下s.txt这个文件,并详细解释一下每一部分的内容。

    第1部分是序列化文件头

    ◇AC ED:STREAM_MAGIC序列化协议

    ◇00 05:STREAM_VERSION序列化协议版本

    ◇73:TC_OBJECT声明这是一个新的对象

    第2部分是要序列化的类的描述,在这里是SerializableObject类

    ◇72:TC_CLASSDESC声明这里开始一个新的class

    ◇00 1F:十进制的31,表示class名字的长度是31个字节

    ◇63 6F 6D ... 65 63 74:表示的是“com.xrq.test.SerializableObject”这一串字符,可以数一下确实是31个字节

    ◇00 00 00 00 00 00 00 01:SerialVersion,序列化ID,1

    ◇02:标记号,声明该对象支持序列化

    ◇00 01:该类所包含的域的个数为1个

    第3部分是对象中各个属性项的描述

    ◇4C:字符"L",表示该属性是一个对象类型而不是一个基本类型

    ◇00 04:十进制的4,表示属性名的长度

    ◇73 74 72 30:字符串“str0”,属性名

    ◇74:TC_STRING,代表一个new String,用String来引用对象

    第4部分是该对象父类的信息,如果没有父类就没有这部分。有父类和第2部分差不多

    ◇00 12:十进制的18,表示父类的长度

    ◇4C 6A 61 ... 6E 67 3B:“L/java/lang/String;”表示的是父类属性

    ◇78:TC_ENDBLOCKDATA,对象块结束的标志

    ◇70:TC_NULL,说明没有其他超类的标志

    第5部分输出对象的属性项的实际值,如果属性项是一个对象,这里还将序列化这个对象,规则和第2部分一样

    ◇00 04:十进制的4,属性的长度

    ◇73 74 72 30:字符串“str0”,str0的属性值

    从以上对于序列化后的二进制文件的解析,我们可以得出以下几个关键的结论:

    1、序列化之后保存的是类的信息

    2、被声明为transient的属性不会被序列化,这就是transient关键字的作用

    3、被声明为static的属性不会被序列化,这个问题可以这么理解,序列化保存的是对象的状态,但是static修饰的变量是属于类的而不是属于变量的,因此序列化的时候不会序列化它

    接下来运行一下上面的代码看一下

    str0 = str0
    str1 = null

    因为str1是一个transient类型的变量,没有被序列化,因此反序列化出来也是没有任何内容的,显示的null,符合我们的结论。

     4.defaultWriteObject和defaultReadObject(手动指定序列化)

    4.1手动指定序列化

    Java并不强求用户非要使用默认的序列化方式,用户也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保证序列化前后能得到想要的数据就好了。手动指定序列化方式的规则是:

    进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列化的过程。

    这是非常有用的。比如:

    1、有些场景下,某些字段我们并不想要使用Java提供给我们的序列化方式,而是想要以自定义的方式去序列化它,比如ArrayList的elementData、HashMap的table(至于为什么在之后写这两个类的时候会解释原因),就可以通过将这些字段声明为transient,然后在writeObject和readObject中去使用自己想要的方式去序列化它们

    2、因为序列化并不安全,因此有些场景下我们需要对一些敏感字段进行加密再序列化,然后再反序列化的时候按照同样的方式进行解密,就在一定程度上保证了安全性了。要这么做,就必须自己写writeObject和readObject,writeObject方法在序列化前对字段加密,readObject方法在序列化之后对字段解密

    上面的例子SerializObject这个类修改一下,主函数不需要修改:

    复制代码
     1 public class SerializableObject implements Serializable
     2 {
     3     private static final long serialVersionUID = 1L;
     4     
     5     private String str0;
     6     private transient String str1;
     7     private static String str2 = "abc";
     8     
     9     public SerializableObject(String str0, String str1)
    10     {
    11         this.str0 = str0;
    12         this.str1 = str1;
    13     }
    14     
    15     public String getStr0()
    16     {
    17         return str0;
    18     }
    19 
    20     public String getStr1()
    21     {
    22         return str1;
    23     }
    24     
    25     private void writeObject(java.io.ObjectOutputStream s) throws Exception
    26     {
    27         System.out.println("我想自己控制序列化的过程");
    28         s.defaultWriteObject();
    29         s.writeInt(str1.length());
    30         for (int i = 0; i < str1.length(); i++)
    31             s.writeChar(str1.charAt(i));
    32     }
    33     
    34     private void readObject(java.io.ObjectInputStream s) throws Exception
    35     {
    36         System.out.println("我想自己控制反序列化的过程");
    37         s.defaultReadObject();
    38         int length = s.readInt();
    39         char[] cs = new char[length];
    40         for (int i = 0; i < length; i++)
    41             cs[i] = s.readChar();
    42         str1 = new String(cs, 0, length);
    43     }
    44 }
    复制代码

    直接看一下运行结果:

    我想自己控制序列化的过程
    我想自己控制反序列化的过程
    str0 = str0
    str1 = str1

    看到,程序走到了我们自己写的writeObject和readObject中,而且被transient修饰的str1也成功序列化、反序列化出来了----因为手动将str1写入了文件和从文件中读了出来。不妨再看一下s.txt文件的二进制:

    看到橘黄色的部分就是writeObject方法追加的str1的内容。至此,总结一下writeObject和readObject的通常用法:

    先通过defaultWriteObject和defaultReadObject方法序列化、反序列化对象,然后在文件结尾追加需要额外序列化的内容/从文件的结尾读取额外需要读取的内容。 

     4.2 通过这种方式达到 序列化static和transient变量的目的

    1 /**
      2  * 序列化的演示测试程序
      3  *
      4  * @author skywang
      5  */
      6 
      7 import java.io.FileInputStream;   
      8 import java.io.FileOutputStream;   
      9 import java.io.ObjectInputStream;   
     10 import java.io.ObjectOutputStream;   
     11 import java.io.Serializable;   
     12 import java.io.IOException;   
     13 import java.lang.ClassNotFoundException;   
     14   
     15 public class SerialTest5 { 
     16     private static final String TMP_FILE = ".serialtest5.txt";
     17   
     18     public static void main(String[] args) {   
     19         // 将“对象”通过序列化保存
     20         testWrite();
     21         // 将序列化的“对象”读出来
     22         testRead();
     23     }
     24   
     25 
     26     /**
     27      * 将Box对象通过序列化,保存到文件中
     28      */
     29     private static void testWrite() {   
     30         try {
     31             // 获取文件TMP_FILE对应的对象输出流。
     32             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
     33             ObjectOutputStream out = new ObjectOutputStream(
     34                     new FileOutputStream(TMP_FILE));
     35             // 创建Box对象,Box实现了Serializable序列化接口
     36             Box box = new Box("desk", 80, 48);
     37             // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
     38             out.writeObject(box);
     39             // 打印“Box对象”
     40             System.out.println("testWrite box: " + box);
     41             // 修改box的值
     42             box = new Box("room", 100, 50);
     43 
     44             out.close();
     45         } catch (Exception ex) {
     46             ex.printStackTrace();
     47         }
     48     }
     49  
     50     /**
     51      * 从文件中读取出“序列化的Box对象”
     52      */
     53     private static void testRead() {
     54         try {
     55             // 获取文件TMP_FILE对应的对象输入流。
     56             ObjectInputStream in = new ObjectInputStream(
     57                     new FileInputStream(TMP_FILE));
     58             // 从对象输入流中,读取先前保存的box对象。
     59             Box box = (Box) in.readObject();
     60             // 打印“Box对象”
     61             System.out.println("testRead  box: " + box);
     62             in.close();
     63         } catch (Exception e) {
     64             e.printStackTrace();
     65         }
     66     }
     67 }
     68 
     69 
     70 /**
     71  * Box类“支持序列化”。因为Box实现了Serializable接口。
     72  *
     73  * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
     74  */
     75 class Box implements Serializable {
     76     private static int width;   
     77     private transient int height; 
     78     private String name;   
     79 
     80     public Box(String name, int width, int height) {
     81         this.name = name;
     82         this.width = width;
     83         this.height = height;
     84     }
     85 
     86     private void writeObject(ObjectOutputStream out) throws IOException{ 
     87         out.defaultWriteObject();//使定制的writeObject()方法可以利用自动序列化中内置的逻辑。 
     88         out.writeInt(height); 
     89         out.writeInt(width); 
     90         //System.out.println("Box--writeObject width="+width+", height="+height);
     91     }
     92 
     93     private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
     94         in.defaultReadObject();//defaultReadObject()补充自动序列化 
     95         height = in.readInt(); 
     96         width = in.readInt(); 
     97         //System.out.println("Box---readObject width="+width+", height="+height);
     98     }
     99 
    100     @Override
    101     public String toString() {
    102         return "["+name+": ("+width+", "+height+") ]";
    103     }
    104 }
    

      

    运行结果

    testWrite box: [desk: (80, 48) ]
    testRead  box: [desk: (80, 48) ]

    程序说明

    “序列化不会自动保存static和transient变量”,因此我们若要保存它们,则需要通过writeObject()和readObject()去手动读写。
    (01) 通过writeObject()方法,写入要保存的变量。writeObject的原始定义是在ObjectOutputStream.java中,我们按照如下示例覆盖即可:

    private void writeObject(ObjectOutputStream out) throws IOException{ 
        out.defaultWriteObject();// 使定制的writeObject()方法可以利用自动序列化中内置的逻辑。 
        out.writeInt(ival);      // 若要保存“int类型的值”,则使用writeInt()
        out.writeObject(obj);    // 若要保存“Object对象”,则使用writeObject()
    }

    (02) 通过readObject()方法,读取之前保存的变量。readObject的原始定义是在ObjectInputStream.java中,我们按照如下示例覆盖即可:

    private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
        in.defaultReadObject();       // 使定制的readObject()方法可以利用自动序列化中内置的逻辑。 
        int ival = in.readInt();      // 若要读取“int类型的值”,则使用readInt()
        Object obj = in.readObject(); // 若要读取“Object对象”,则使用readObject()
    }

    至此,我们就介绍完了“序列化对static和transient变量的处理”。

    5.Externalizable 

    如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。

    Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性。

    需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。

    /**
     * 序列化的演示测试程序
     *
     * @author skywang
     */
    
    import java.io.FileInputStream;   
    import java.io.FileOutputStream;   
    import java.io.ObjectInputStream;   
    import java.io.ObjectOutputStream;   
    import java.io.ObjectOutput;   
    import java.io.ObjectInput;   
    import java.io.Serializable;   
    import java.io.Externalizable;   
    import java.io.IOException;   
    import java.lang.ClassNotFoundException;   
      
    public class ExternalizableTest2 { 
        private static final String TMP_FILE = ".externalizabletest2.txt";
      
        public static void main(String[] args) {   
            // 将“对象”通过序列化保存
            testWrite();
            // 将序列化的“对象”读出来
            testRead();
        }
      
    
        /**
         * 将Box对象通过序列化,保存到文件中
         */
        private static void testWrite() {   
            try {
                // 获取文件TMP_FILE对应的对象输出流。
                // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
                ObjectOutputStream out = new ObjectOutputStream(
                        new FileOutputStream(TMP_FILE));
                // 创建Box对象
                Box box = new Box("desk", 80, 48);
                // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
                out.writeObject(box);
                // 打印“Box对象”
                System.out.println("testWrite box: " + box);
    
                out.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
     
        /**
         * 从文件中读取出“序列化的Box对象”
         */
        private static void testRead() {
            try {
                // 获取文件TMP_FILE对应的对象输入流。
                ObjectInputStream in = new ObjectInputStream(
                        new FileInputStream(TMP_FILE));
                // 从对象输入流中,读取先前保存的box对象。
                Box box = (Box) in.readObject();
                // 打印“Box对象”
                System.out.println("testRead  box: " + box);
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    /**
     * Box类实现Externalizable接口
     */
    class Box implements Externalizable {
        private int width;   
        private int height; 
        private String name;   
    
        public Box() {
        }
    
        public Box(String name, int width, int height) {
            this.name = name;
            this.width = width;
            this.height = height;
        }
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(name);
            out.writeInt(width);
            out.writeInt(height);
        }
    
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            name = (String) in.readObject();
            width = in.readInt();
            height = in.readInt();
        }
    
        @Override
        public String toString() {
            return "["+name+": ("+width+", "+height+") ]";
        }
    }
    

      

    运行结果

    testWrite box: [desk: (80, 48) ]
    testRead  box: [null: (0, 0) ]

    注意事项:

    (01) 实现Externalizable接口的类,不会像实现Serializable接口那样,会自动将数据保存。
    (02) 实现Externalizable接口的类,必须实现writeExternal()和readExternal()接口!否则,程序无法正常编译!
    (03) 实现Externalizable接口的类,必须定义不带参数的构造函数!会默认的调用构造函数,否则,程序无法正常编译!
    (04) writeExternal() 和 readExternal() 的方法都是public的,不是非常安全!

    6.复杂序列化情况总结

    虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,最后对一些复杂的对象情况作一个总结:

    1、当父类继承Serializable接口时,所有子类都可以被序列化

    2、子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据丢失),但是在子类中属性仍能正确序列化

    3、如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错

    4、反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错

    5、反序列化时,如果serialVersionUID被修改,则反序列化时会失败

    转载:http://www.cnblogs.com/xrq730/p/4821958.html

    http://www.cnblogs.com/skywang12345/p/io_06.html

  • 相关阅读:
    Win10下IIS配置图解、MVC项目发布图解、IIS添加网站图解
    小猴子下落(二叉树,思维)
    Curious Robin Hood(树状数组+线段树)
    表达式求值(后缀表达式求值)
    郁闷的C小加(一)(后缀表达式)
    最小公倍数(大数)
    修路方案(次小生成树)
    Cipher(置换群)
    Cow Sorting(置换群)
    Necklace of Beads(polya计数)
  • 原文地址:https://www.cnblogs.com/haitaofeiyang/p/7694109.html
Copyright © 2011-2022 走看看