• 序列化


    一、序列化的用途

    序列化的主要用途有两个,一个是对象持久化,另一个是跨网络的数据交换、远程过程调用。

    二、各种序列化机制比较

    (跨语言、序列化大小、性能)
    1、(000)Java标准的序列化机制有很多优点,使用简单,可自动处理对象引用和循环引用,也可以方便的进行定制,处理版本问题等,但它也有一些重要的局限性:
      Java序列化格式是一种私有格式,是一种Java语言特有的技术,不能被其他语言识别,不能实现跨语言的数据交换。
      Java在序列化字节中保存了很多描述信息,使得序列化格式比较大。
      Java的默认序列化使用反射分析遍历对象结构,性能比较低。
      Java的序列化格式是二进制的,不方便查看和修改。
    2、(100)由于这些局限性,实践中往往会使用一些替代方案。在跨语言的数据交换格式中,XML/JSON是被广泛采用的文本格式,各种语言都有对它们的支持,文件格式清晰易读,有很多查看和编辑工具,它们的不足之处是性能和序列化大小。
    3、(111)在性能和大小敏感的领域,往往会采用更为精简高效的二进制方式如ProtoBuf, Thrift, MessagePack等

    三、Java序列化

    Java中有两个接口用于序列化:java.io.Serializable(默认序列化)、java.io.Externalizable(自定义序列化)

    (一)默认序列化

    1、实现java.io.Serializable即可被序列化,Serializable没有定义任何方法,只是一个标记接口。
    默认序列化能hold住多个对象引用同一对象、对象间循环引用等情况,重复序列化同一个对象时只在第一次把对象转换成字节序列,以后只输出前面的序列化编号
    静态属性不会被序列化,因为本身就在类中;方法也不会被序列化

    2、默认的序列化机制已经很强大了,它可以自动将对象中的所有字段自动保存和恢复,但这种默认行为有时候不是我们想要的。
    对于有些字段,它的值可能与内存位置有关,比如默认的hashCode()方法的返回值,当恢复对象后,内存位置肯定变了,基于原内存位置的值也就没有了意义。还有一些字段,可能与当前时间有关,比如表示对象创建时的时间,保存和恢复这个字段就是不正确的。

    如果类中的字段表示的是类的实现细节,而非逻辑信息,那默认序列化也是不适合的。因为序列化格式表示一种契约,应该描述类的逻辑结构,而非与实现细节相绑定,绑定实现细节将使得难以修改,破坏封装。如LinkedList,它的默认序列化就是不适合的,因为LinkedList表示一个List,它的逻辑信息是列表的长度,以及列表中的每个对象,但LinkedList类中的字段表示的是链表的实现细节, 如头尾节点指针,对每个节点,还有前驱和后继节点指针等。

    (二)定制序列化

    1、对于java.io.Serializable,可以通过transient、writeObject/writeObject、readResolve/writeReplace实现定制序列化

    1. 可以用 transient 修饰不想被Java自动序列化的属性,再用Java默认的序列化。(transient可且只可用于修饰不想被Java自动序列化的属性)
    2. 或者通过实现方法 void writeObject(java.io.ObjectOutputStream out)、void readObject(java.io.ObjectInputStream in) 来实现自定义的序列化逻辑
    3.  实现 Object writeReplace() 替换。如果定义了该方法,在序列化时,会先调用该方法,该方法的返回值才会被当做真正的对象进行序列化;实现 Object readResolve() 还原(自实现的枚举类、 单例中)。如果定义了该方法,在反序列化之后,会额外调用该方法,该方法的返回值才会被当做真正的反序列化的结果。这个方法通常用于反序列化单例对象的场景。(即序列化时的执行顺序是: writeReplace、writeObject、readObject、readResolve)

    2、实现java.io.Externalizable接口,并实现两个方法:  void readExternal(ObjectInput in)、void writeExternal(ObjectOutput out) 。

    • Externalizable继承于Serializable,当使用该接口时,之前基于Serializable接口的序列化机制就将失效,序列化的细节需要由程序员去完成。基于此,可以减少序列化后文件的大小。
    • 实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。因为使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。
    • 默认的序列化机制由于需要分析对象结构,往往比较慢,通过实现Externalizable接口,可以提高性能

    示例:如下代码中,实现Serializable比实现Externalizable时的序列化文件大,且前者中有out.defaultWriteObject();/in.defaultReadObject();比没有时序列化后文件大。但它们功能都一样。

     1 //public class Fuck implements Serializable {
     2 public class Fuck implements Serializable {
     3     /**
     4      * 
     5      */
     6     private static final long serialVersionUID = -3466537175580251255L;
     7     public byte age;
     8     public String name;
     9 
    10     public Fuck() {
    11 
    12     }
    13 
    14     public Fuck(byte age, String name) {
    15         this.age = age;
    16         this.name = name;
    17     }
    18 
    19     @Override
    20     public String toString() {
    21         return "[" + age + "," + name + "]";
    22     }
    23 
    24     public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    25         String fileName = "tmp0.out";
    26         ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName));
    27         objectOutputStream.writeObject(new Fuck((byte) 1, "小华"));
    28         objectOutputStream.flush();
    29         objectOutputStream.close();
    30 
    31         ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName));
    32         Fuck fuck = (Fuck) objectInputStream.readObject();
    33         System.out.println(fuck);
    34         objectInputStream.close();
    35     }
    36 
    37     private void writeObject(java.io.ObjectOutputStream out) {
    38         try {
    39             // out.defaultWriteObject();
    40             out.writeByte(age);
    41             out.writeObject(name);
    42         } catch (Exception e) {
    43             e.printStackTrace();
    44         }
    45     }
    46 
    47     private void readObject(java.io.ObjectInputStream in) {
    48         try {
    49             // in.defaultReadObject();
    50             this.age = in.readByte();
    51             this.name = (String) in.readObject();
    52         } catch (Exception e) {
    53             e.printStackTrace();
    54         }
    55     }
    56 
    57     public void writeExternal(ObjectOutput out) throws IOException {
    58         out.writeByte(age);
    59         out.writeObject(name);
    60     }
    61 
    62     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    63         this.age = in.readByte();
    64         this.name = (String) in.readObject();
    65     }
    66 }
    View Code

    (三)示例

      1 public class _10_IO_SerialTest {
      2 
      3     public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
      4         // TODO Auto-generated method stub
      5         // BasicTest
      6         // BasicTest();
      7 
      8         // customSerialTest
      9         // customSerialTestOne();
     10         // customSerialTestTwo();
     11         // customSerialTestThree();
     12         customSerialTestFour();
     13     }
     14 
     15     // 基本测试
     16     private static void BasicTest() throws FileNotFoundException, IOException, ClassNotFoundException {
     17         // 系统默认序列化,默认writeObject、readObject
     18         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
     19         ArrayList<Person> lst = new ArrayList<Person>();
     20         {
     21             lst.add(new Person("小张", 22));
     22             lst.add(new Person("小李", 23));
     23             // 以下两次输出同一个对象lst
     24             oos.writeObject(lst);
     25             lst.get(0).setAge(11);
     26             lst.remove(1);
     27             oos.writeObject(lst);
     28 
     29             oos.writeObject(new Person("小王", 24));
     30         }
     31 
     32         // 反序列化
     33         {
     34             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
     35             lst = (ArrayList<Person>) ois.readObject();
     36             System.out.println(lst.size());// 2
     37             lst = (ArrayList<Person>) ois.readObject();
     38             System.out.println(lst.size());// 2
     39             System.out.println(lst.get(0).getAge());// 22
     40             // 从上面两次输出反应的都是lst改变前的状态,说明重复序列化同一个对象时只在第一次把对象转换成字节序列。
     41 
     42             Person p = (Person) ois.readObject();
     43             System.out.printf("%s %s %s %s
    ", p.getAge(), p.getName(), p.getHome(), p.getNumber());// transient修饰的home字段为null说明没有被序列化
     44         }
     45     }
     46 
     47     // 自定义序列化测试1
     48     private static void customSerialTestOne() throws FileNotFoundException, IOException, ClassNotFoundException {
     49         // -----自定义序列化:写入、读出
     50         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
     51         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
     52         //
     53         PersonTypeOne ptt = new PersonTypeOne("小虎", 11);
     54         oos.writeObject(ptt);
     55         //
     56         ptt = (PersonTypeOne) ois.readObject();
     57         System.out.println(ptt.getAge() + " " + ptt.getName());
     58     }
     59 
     60     // 自定义序列化测试2
     61     private static void customSerialTestTwo() throws FileNotFoundException, IOException, ClassNotFoundException {
     62         // -----自定义序列化:对象替换writeReplace
     63         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
     64         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
     65 
     66         // 写,调用PersonTypeTwo的writeReplace方法,对象替换为String
     67         PersonTypeTwo ptt = new PersonTypeTwo("小虎", 11);
     68         oos.writeObject(ptt);
     69 
     70         // 读,已是ArrayList类型
     71         ArrayList<Object> pttList = (ArrayList<Object>) ois.readObject();
     72         System.out.println(pttList.get(0) + " " + pttList.get(1));
     73     }
     74 
     75     // 自定义序列化测试3
     76     @SuppressWarnings("unused")
     77     private static void customSerialTestThree() throws FileNotFoundException, IOException, ClassNotFoundException {
     78         // -----自定义序列化:自定义enum的问题的解决readResolve
     79         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
     80         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
     81 
     82         // 没有readResolve
     83         oos.writeObject(Season_0.SPRING);
     84         System.out.println(((Season_0) ois.readObject()) == Season_0.SPRING);// false,可见反序列化后得到的并不是我们希望的枚举常量SPRING
     85         // 有readResolve
     86         oos.writeObject(Season_1.SPRING);
     87         System.out.println(((Season_1) ois.readObject()) == Season_1.SPRING);// true,通过readResolve解决上述问题
     88     }
     89 
     90     // 自定义序列化测试4
     91     private static void customSerialTestFour() throws FileNotFoundException, IOException, ClassNotFoundException {
     92         // 通过实现Externalizable接口实现的序列化,必须实现两个抽象方法,其他的用法与Serializable一样。
     93         // 使用很少,一般通过实现Serializable接口实现序列化
     94         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
     95         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
     96         //
     97         PersonTypeThree ptt = new PersonTypeThree("xiaozhou", 30);
     98         oos.writeObject(ptt);
     99 
    100         //
    101         ptt = (PersonTypeThree) ois.readObject();
    102         System.out.println(ptt.getAge() + " " + ptt.getName());
    103     }
    104 }
    105 
    106 class Person implements java.io.Serializable {
    107 
    108     /**
    109      * 默认序列化,一个个字段输出,读入时再按同样顺序一个个读入
    110      */
    111     private static final long serialVersionUID = -2032185216332524429L;
    112     private String name;
    113     private int age;
    114     transient private String home = "北京";
    115     private static int number = 3;// 静态属性不会被序列化,因为本身就在类中
    116 
    117     public Person(String name, int age) {
    118         this.age = age;
    119         this.name = name;
    120     }
    121 
    122     public String getHome() {
    123         return home;
    124     }
    125 
    126     public void setHome(String home) {
    127         this.home = home;
    128     }
    129 
    130     public String toString() {
    131         return name + " " + age;
    132     }
    133 
    134     public String getName() {
    135         return name;
    136     }
    137 
    138     public void setName(String name) {
    139         this.name = name;
    140     }
    141 
    142     public int getAge() {
    143         return age;
    144     }
    145 
    146     public void setAge(int age) {
    147         this.age = age;
    148     }
    149 
    150     public static int getNumber() {
    151         return number;
    152     }
    153 
    154     public static void setNumber(int number) {
    155         Person.number = number;
    156     }
    157 }
    158 
    159 class PersonTypeOne implements java.io.Serializable {
    160     /**
    161      * 自定义序列化,可以自己确定什么顺序输出哪些东西,反序列化时再同序读入,否则出错<br/>
    162      * private void writeObject(java.io.ObjectOutputStream out)<br>
    163      * private void readObject(java.io.ObjectInputStream in)<br>
    164      * private void readObjectNoData()<br>
    165      */
    166     private static final long serialVersionUID = 5990380364728171582L;
    167     private String name;
    168     private int age;
    169 
    170     private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    171         out.writeInt(age);
    172         out.writeObject(new StringBuffer(name).reverse());// 反转name
    173     }
    174 
    175     private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, IOException {
    176         // 所读取的内容的顺序须与写的一样,否则出错
    177         this.age = in.readInt();
    178         this.name = ((StringBuffer) in.readObject()).reverse().toString();
    179     }
    180 
    181     public PersonTypeOne(String name, int age) {
    182         this.name = name;
    183         this.age = age;
    184     }
    185 
    186     public String getName() {
    187         return name;
    188     }
    189 
    190     public void setName(String name) {
    191         this.name = name;
    192     }
    193 
    194     public int getAge() {
    195         return age;
    196     }
    197 
    198     public void setAge(int age) {
    199         this.age = age;
    200     }
    201 
    202 }
    203 
    204 class PersonTypeTwo implements java.io.Serializable {
    205     /**
    206      * 自定义序列化,写入时对象替换。系统在序列化某个对象前会自动先后调用writeReplace、writeObject方法<br>
    207      * ANY-ACCESS-MODIFIER Object writeReplace()
    208      */
    209     private static final long serialVersionUID = -7958822199382954305L;
    210     private String name;
    211     private int age;
    212 
    213     // private void writeReplace() {
    214     // // java序列化机制在序列化对象前总先调用该对象的writeReplace方法,若方法返回另一java对象则转为序列化该对象否则序列化原对象
    215     // }
    216 
    217     private Object writeReplace() {
    218         // java序列化机制在序列化对象前总先调用该对象的writeReplace方法,若方法返回另一java对象则转为序列化该对象否则序列化原对象
    219         ArrayList<Object> al = new ArrayList<Object>();
    220         al.add(age);
    221         al.add(name);
    222         return al;
    223     }
    224 
    225     public PersonTypeTwo(String name, int age) {
    226         this.name = name;
    227         this.age = age;
    228     }
    229 
    230     public String getName() {
    231         return name;
    232     }
    233 
    234     public void setName(String name) {
    235         this.name = name;
    236     }
    237 
    238     public int getAge() {
    239         return age;
    240     }
    241 
    242     public void setAge(int age) {
    243         this.age = age;
    244     }
    245 }
    246 
    247 class Season_0 implements java.io.Serializable {
    248     // readResolve() test
    249     private int id;
    250 
    251     private Season_0(int id) {
    252         this.id = id;
    253     }
    254 
    255     public static final Season_0 SPRING = new Season_0(1);
    256     public static final Season_0 SUMMER = new Season_0(2);
    257     public static final Season_0 FALL = new Season_0(3);
    258     public static final Season_0 WINTER = new Season_0(4);
    259 }
    260 
    261 class Season_1 implements java.io.Serializable {
    262     // readResolve() test
    263     /**
    264      * 与上面的Season_0相比,多了readResolve方法。<br>
    265      * 反序列化机制会在调用readObject后调用readResolve方法, 与writeReplace相对应<br>
    266      * ANY-ACCESS-MODIFIER Object readResolve()
    267      */
    268     private int id;
    269 
    270     private Season_1(int id) {
    271         this.id = id;
    272     }
    273 
    274     public static final Season_1 SPRING = new Season_1(1);
    275     public static final Season_1 SUMMER = new Season_1(2);
    276     public static final Season_1 FALL = new Season_1(3);
    277     public static final Season_1 WINTER = new Season_1(4);
    278 
    279     private Object readResolve() {
    280         // 系统会在调用readObject后调用此方法
    281         switch (id) {
    282         case 1:
    283             return SPRING;
    284         case 2:
    285             return SUMMER;
    286         case 3:
    287             return FALL;
    288         case 4:
    289             return WINTER;
    290         default:
    291             return null;
    292         }
    293     }
    294 }
    295 
    296 class PersonTypeThree implements java.io.Externalizable {
    297     private String name;
    298     private int age;
    299 
    300     public PersonTypeThree() {
    301         // 继承Externalizable实现的序列化必须要有此构造方法
    302     }
    303 
    304     public PersonTypeThree(String name, int age) {
    305         this.name = name;
    306         this.age = age;
    307     }
    308 
    309     public String getName() {
    310         return name;
    311     }
    312 
    313     public void setName(String name) {
    314         this.name = name;
    315     }
    316 
    317     public int getAge() {
    318         return age;
    319     }
    320 
    321     public void setAge(int age) {
    322         this.age = age;
    323     }
    324 
    325     // 通过实现Externalizable接口实现的序列化必须实现如下两个方法,其他的用法与Serializable一样。
    326     @Override
    327     public void writeExternal(ObjectOutput out) throws IOException {
    328         // TODO Auto-generated method stub
    329         out.writeObject(new StringBuffer(name).reverse());
    330         out.writeInt(age);
    331         System.out.println("writeExternal run");
    332     }
    333 
    334     @Override
    335     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    336         // TODO Auto-generated method stub
    337         this.name = ((StringBuffer) in.readObject()).reverse().toString();
    338         this.age = in.readInt();
    339         System.out.println("readExternal run");
    340     };
    View Code

    其他:

    子类序列化时:

    如果父类实现了Serializable接口,则父类和子类都可以序列化。

    如果父类没有实现Serializable接口:若也没有提供默认构造函数,那么子类的序列化会出错;若提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。

        

     四、参考资料

    1、http://mp.weixin.qq.com/s/y3b3prB54Mj1JN2CEURyrg-码农翻身

    2、《疯狂Java讲义》

  • 相关阅读:
    设计模式20-观察者模式
    设计模式19-备忘录模式
    设计模式18-中介者模式
    设计模式17-迭代器模式
    设计模式16-解释器模式
    Wireshark基本介绍和学习TCP三次握手
    Jmeter CSV 参数化/检查点/断言
    Jmeter运营活动并发测试—巧用集合点
    一个简单的性能测试
    Jmeter对HTTP请求压力测试、并发测试
  • 原文地址:https://www.cnblogs.com/z-sm/p/6734430.html
Copyright © 2020-2023  润新知