zoukankan      html  css  js  c++  java
  • Java序列化

    一、简介

    定义:

    序列化:序列化是将对象转换为字节流。

    反序列化:范序列化是将字节流转换为对象。

    序列化的作用有:

    序列化可以将对象的字节序列持久化保存在内存、文件、数据库中。

    在网络上传送对象的字节序列。

    RMI(远程方法调用)

    二、序列化和反序列化

    Java通过对象输入输出流来实现序列化和反序列化:

    序列化:java.io.ObjectOutputStream类的writeObject()方法可以实现序列化。

    反序列化:java.io.ObjectInputStream类的readObject()方法用于实现反序列化。

    package com.zhanzhuang.test01;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.OutputStream;
    import java.io.Serializable;
    
    public class SerializeDemo01 {
        enum Sex {
            MALE, FEMALE
        }
    
        static class Person implements Serializable {
            private static final long serialBersionUID = 1L;
            private String name = null;
            private Integer age = null;
            private Sex sex;
    
            public Person() {
                System.out.println("call Person()");
            }
    
            public Person(String name, Integer age, Sex sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
            }
    
            @Override
            public String toString() {
                return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
            }
    
        }
    
        /**
         * 序列化
         */
        private static void serialize(String filename) throws IOException {
            File f = new File(filename); // 定义保存路径
            OutputStream out = new FileOutputStream(f); // 文件输出流
            ObjectOutputStream oos = new ObjectOutputStream(out); // 对象输出流
            oos.writeObject(new Person("Jack", 30, Sex.MALE)); // 保存对象
            oos.close();
            out.close();
        }
    
        /**
         * 反序列化
         */
        private static void deserialize(String filename) throws IOException, ClassNotFoundException {
            File f = new File(filename); // 定义保存路径
            InputStream in = new FileInputStream(f); // 文件输入流
            ObjectInputStream ois = new ObjectInputStream(in); // 对象输入流
            Object obj = ois.readObject(); // 读取对象
            ois.close();
            in.close();
            System.out.println(obj);
        }
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            final String filename = "d:/text.dat";
            serialize(filename);
            deserialize(filename);
    //        File f = new File("d:/zhan.txt");
        }
    
    }

    输出:Person [name=Jack, age=30, sex=MALE]

    三、Serializable接口

    被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

    如果不是Enum、Array的类、如果需要序列化、必须实现java.io.Serializable接口,否则将抛出NotSerializableException异常。这是因为:在序列化操作过程中会对类型进行检查,如果不满足序列化类型要求,就会抛出异常。

    下面做一个小尝试,将SerializeDemo01.java中的内部类Person改为如下实现,继续执行查看运行结果。

    果然不出所料,抛出了NotSerializableException异常。

    四、serialVersionUID

    serialVersionUID这个字段,我们在Java世界中的无数个类中看到这个字段。

    serialVersionUID有什么作用,如何使用serialVersionUID?

    它是Java为每个序列化类产生的版本标识。它可以用来保证在反序列化时,发送方发送和接收方接受的是可兼容的对象。

    如果接收方接收的类的serialVersionUID与发送方发送的serialVersionUID不一致,会抛出InvalidClassException。

    注意:如果可序列化类没有声明serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认serialVersionUID值。尽管这样,还是建议在每一个序列化的类中声名serialVersionUID的值。

    因为不同的jdk编译很可能会生成不同的serialVersionUID默认值,从而导致在反序列化时抛出InvalidClassException异常。

    serialVersionUID字段必须是static final long类型的。

    我们来举一个例子:

    ①在版本信息为1L的时候进行序列化后执行反序列化(序列化为1L,反序列化为1L。我猜不会报错):

    执行后输出:Person [name=Jack, age=30, sex=MALE]。

    ②在开发过程中修改了内容,所以要修改序列化的版本信息,但是没有进行序列化,直接进行反序列化(序列化为1L,反序列化为2L。我猜会报错):

    会抛出异常:

     由于在②步骤中修改了内容,导致与版本不兼容,所以要修改序列化版本号

    综上所述:

    我们大概可以清楚:serialVersionUID 用于控制序列化版本是否兼容。若我们认为修改的可序列化类是向后兼容的,则不修改 serialVersionUID。

     五、默认序列化机制

    如果仅仅只是让某个类实现Serializable接口,而没有其他任何处理的话,那么就是使用默认序列化机制。

    使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对其父类的字段以及该对象引用的其他对象也进行序列化。同样的,这些其他对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会比较复杂,开销也比较大。

    注意:这里的父类和引用对象既然要进行序列化,那么他们当然也要满足序列化要求:被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

    六、非默认序列化机制

    在现实应用中,有些时候不能用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。

    transient关键字

    当某个字段被声明为transient后,默认序列化机制就会忽略该字段。

    我们将SerializeDemo01示例中的内部类Person的age字段声明为transient,如下所示:

    public class SerializeDemo02 {
        static class Person implements Serializable {
            transient private Integer age = null;
            // 其他内容略
        }
        // 其他内容略
    }

    输出:Person [name=Jack, age=null, sex=MALE]

    从输出的结果可以看出,age字段没有被序列化。

     

  • 相关阅读:
    C++设计模式之-代理模式
    C++实现设计模式之-装饰模式
    C++实现设计模式之 —策略与简单工程结合
    C++笔记(5)——浮点数的比较
    PAT 1001 A+B Format (20 point(s))
    LeetCode——28. Implement strStr()
    LeetCode——3. Longest Substring Without Repeating Characters
    LeetCode——160 Intersection of Two Linked Lists
    LeetCode——142 设计链表2
    LeetCode——141 设计链表
  • 原文地址:https://www.cnblogs.com/zhanzhuang/p/9140689.html
Copyright © 2011-2022 走看看