美文网首页
Java序列化

Java序列化

作者: 茶还是咖啡 | 来源:发表于2019-10-24 08:13 被阅读0次

    众所周知,类的对象会随着程序的终止而被垃圾收集器销毁。如果要在不重新创建对象的情况下调用该类,该怎么做?
    这就可以通过序列化将数据转换为字节流。
    对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序;从字节流创建对象的相反的过程称为反序列化。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。
    [摘抄自百度百家号]https://baijiahao.baidu.com/s?id=1622011683975285944&wfr=spider&for=pc侵,删。

    1. 序列化一个对象

    一个对象如果向被序列化,那么需要实现Serializable接口,该接口没有任何方法,只是用于标识该类产生的对象可以被序列化。如果没有实现改接口,无法进行序列化该对象,序列化时会抛出java.io.NotSerializableException异常。
    除了实现Serializable接口外,还需要对象声明一类型为private static final long类型的序列化id即,serialVersionUID,用于对象反序列化时检测是否是同一个类,是否可以进行反序列化标识的id.
    eg:

    @Data
    public class Person implements Serializable {
    
        private static Long serialVersionUID = 32584769L;
    
        private String name;
    
        private Integer age;
    
    }
    
    1. Main
        public static void main(String[] args) throws IOException {
            Person person = new Person();
            person.setAge(18);
            person.setName("小明");
    
            try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("person")))) {
                objectOutputStream.writeObject(person);
            }
        }
    
    1. 序列化后的对象
    �� �sr �org.ywb.pojo.Person��7�w~��� �L �aget �Ljava/lang/Integer;L �namet �Ljava/lang/String;xpsr �java.lang.Integer�⠤���8� �I �valuexr �java.lang.Number���������  xp   �t �小明
    

    序列成字节后的对象我们隐约可以看出,里面包含包名,属性名称,属性值,属性类型等信息。

    1. 反序列化对象
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("person")))) {
                Person person = (Person)objectInputStream.readObject();
                System.out.println(person.toString());
            }
        }
    

    输出

    Person(name=小明, age=18)
    

    静态变量序列化

    序列化是对象的行为,静态变量属于类,不属于对象,所以不会序列化静态变量。
    eg:

    @Data
    public class Person implements Serializable {
        ...
        private static String nickName = "李四";
    
        public static String getNickName() {
            return nickName;
        }
    
        public static void setNickName(String nickName) {
            Person.nickName = nickName;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", nickName="+nickName+
                    '}';
        }
    }
    
    

    对象原属性不变,为对象新增一个静态变量nickName,并初始化为“李四”。

    1. Main
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Person person = new Person();
            person.setAge(18);
            person.setName("小明");
    
    
            try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("person")))) {
                objectOutputStream.writeObject(person);
            }
    
            Person.setNickName("张三");
            try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("person")))) {
                Person p = (Person)objectInputStream.readObject();
                System.out.println(p.toString());
            }
        }
    }
    
    

    对对象进行序列化,序列化后,更改静态变量的是为:“张三”,如果静态变量也被序列化了,那么nickName的值不会受“张三”的影响,依旧是“李四”。但是程序输出结果为:

    Person{name='小明', age=18, nickName=张三}
    

    说明在序列化时,静态属性并不会被序列化。

    transient 关键字

    在对象序列化时,非静态属性都会被序列化,但是有时候对象存储的属性信息非常敏感,如密码信息,所以这些属性不想被序列化,这时,我们只需要在属性前加transient关键字即可。
    eg:

    1. 原有属性不变,新增password字段
        transient private String password;
    
    1. Main
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Person person = new Person();
            person.setAge(18);
            person.setName("小明");
            person.setPassword("123456");
    
    
            try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("person")))) {
                objectOutputStream.writeObject(person);
            }
    
            Person.setNickName("张三");
            try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("person")))) {
                Person p = (Person)objectInputStream.readObject();
                System.out.println(p.toString());
            }
        }
    

    输出

    Person{name='小明', age=18, nickName=张三, password=null}
    

    final修饰的属性

    final变量将直接通过值参与序列化,所以将final变量声明为transient变量不会产生任何影响。

    1. 新增属性sex
    transient private final String sex = "男";
    
    1. 输出
    Person{name='小明', age=18, nickName=张三, password=null, sex=男}
    

    父子类序列化

    如果子类实现了序列化接口,但是父类没有实现序列化接口,而且父类属性没有初始化,那么,无法序列化父类的属性的值,如果父类属性的值进行了初始化,序列化后的值为初始化后的值,即使子类进行属性的更改也是无效的。
    所以我们在涉及到父子类序列化问题时,一定要考虑父类是否需要实现序列化接口
    eg:

    1. 新增User类,并让Person类继承User
    @Data
    public class User {
        private String id;
    }
    public class Person extends User implements Serializable {
    ...
    }
    

    输出:

    Person{name='小明', age=18, nickName=张三, password=null, sex=男, id=null}
    
    1. 设置id初始值为10
    @Data
    public class User {
        private String id = 10;
    }
    
    1. 子类重新设置id的值为100,输出:
    Person{name='小明', age=18, nickName=张三, password=null, sex=男, id=10}
    

    序列化存储规则

    如果对同一个对象多次序列化,只有第一次会将对象转换成字节流,剩下的会只存储之前序列化对象的引用。

    1. 序列化一个Person对象
    ac ed 00 05 73 72 00 13 6f 72 67 2e 79 77 62 2e
    70 6f 6a 6f 2e 50 65 72 73 6f 6e f3 9b 7b c8 d6
    7f c6 dc 02 00 02 4c 00 03 61 67 65 74 00 13 4c
    6a 61 76 61 2f 6c 61 6e 67 2f 49 6e 74 65 67 65
    72 3b 4c 00 04 6e 61 6d 65 74 00 12 4c 6a 61 76
    61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 72
    00 11 6f 72 67 2e 79 77 62 2e 70 6f 6a 6f 2e 55
    73 65 72 b9 2c d6 38 e6 ed 15 c8 02 00 01 4c 00
    02 69 64 71 00 7e 00 02 78 70 74 00 03 31 30 30
    73 72 00 11 6a 61 76 61 2e 6c 61 6e 67 2e 49 6e
    74 65 67 65 72 12 e2 a0 a4 f7 81 87 38 02 00 01
    49 00 05 76 61 6c 75 65 78 72 00 10 6a 61 76 61
    2e 6c 61 6e 67 2e 4e 75 6d 62 65 72 86 ac 95 1d
    0b 94 e0 8b 02 00 00 78 70 00 00 00 12 74 00 06
    e5 b0 8f e6 98 8e                              
    
    1. 对同一个Person对象序列化两次
     ac ed 00 05 73 72 00 13 6f 72 67 2e 79 77 62 2e
     70 6f 6a 6f 2e 50 65 72 73 6f 6e f3 9b 7b c8 d6
     7f c6 dc 02 00 02 4c 00 03 61 67 65 74 00 13 4c
     6a 61 76 61 2f 6c 61 6e 67 2f 49 6e 74 65 67 65
     72 3b 4c 00 04 6e 61 6d 65 74 00 12 4c 6a 61 76
     61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 72
     00 11 6f 72 67 2e 79 77 62 2e 70 6f 6a 6f 2e 55
     73 65 72 b9 2c d6 38 e6 ed 15 c8 02 00 01 4c 00
     02 69 64 71 00 7e 00 02 78 70 74 00 03 31 30 30
     73 72 00 11 6a 61 76 61 2e 6c 61 6e 67 2e 49 6e
     74 65 67 65 72 12 e2 a0 a4 f7 81 87 38 02 00 01
     49 00 05 76 61 6c 75 65 78 72 00 10 6a 61 76 61
     2e 6c 61 6e 67 2e 4e 75 6d 62 65 72 86 ac 95 1d
     0b 94 e0 8b 02 00 00 78 70 00 00 00 12 74 00 06
     e5 b0 8f e6 98 8e 71 00 7e 00 04               
    

    可以看出第二个对象只是多了5个字节的引用。

    1. 先序列化第一个对象,然后更改name属性,再对该对象进行序列化
    ac ed 00 05 73 72 00 13 6f 72 67 2e 79 77 62 2e
    70 6f 6a 6f 2e 50 65 72 73 6f 6e f3 9b 7b c8 d6
    7f c6 dc 02 00 02 4c 00 03 61 67 65 74 00 13 4c
    6a 61 76 61 2f 6c 61 6e 67 2f 49 6e 74 65 67 65
    72 3b 4c 00 04 6e 61 6d 65 74 00 12 4c 6a 61 76
    61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 72
    00 11 6f 72 67 2e 79 77 62 2e 70 6f 6a 6f 2e 55
    73 65 72 b9 2c d6 38 e6 ed 15 c8 02 00 01 4c 00
    02 69 64 71 00 7e 00 02 78 70 74 00 03 31 30 30
    73 72 00 11 6a 61 76 61 2e 6c 61 6e 67 2e 49 6e
    74 65 67 65 72 12 e2 a0 a4 f7 81 87 38 02 00 01
    49 00 05 76 61 6c 75 65 78 72 00 10 6a 61 76 61
    2e 6c 61 6e 67 2e 4e 75 6d 62 65 72 86 ac 95 1d
    0b 94 e0 8b 02 00 00 78 70 00 00 00 12 74 00 06
    e5 b0 8f e6 98 8e 71 00 7e 00 04               
    

    序列化的字节和第2步的字节是一样的,可以看出,即使对属性进行更改,那么也不会被同步到磁盘中,属性的是不会变化,依然后最初的属性值。
    code:

        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Person person = new Person();
            person.setAge(18);
            person.setName("小明");
            person.setPassword("123456");
            try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("person")))) {
                objectOutputStream.writeObject(person);
                person.setName("王五");
                objectOutputStream.writeObject(person);
            }
            try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("person")))) {
                Person p = (Person)objectInputStream.readObject();
                System.out.println(p.toString());
                Person p1 = (Person)objectInputStream.readObject();
                System.out.println(p1.toString());
                System.out.println(p.equals(p1));
            }
        }
    

    输出:

    Person{name='小明', age=18, nickName=李四, password=null, sex=男, id=10}
    Person{name='小明', age=18, nickName=李四, password=null, sex=男, id=10}
    true
    

    序列化的应用

    序列化的应用有很多种,我在开发中应用序列化最多的就是讲对象序列化后保存到Redis中,还有就是在web开发中对对象进行json序列化然后通过网络发送给客户端。除了这些,还有就是对对象进行深克隆。

    对象深克隆

        @SuppressWarnings("unchecked")
        public <T> T deepClone(T t) throws IOException, ClassNotFoundException {
            ObjectOutputStream objectOutputStream = null;
            ObjectInputStream objectInputStream = null;
            try {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
                objectOutputStream.writeObject(t);
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                objectInputStream = new ObjectInputStream(byteArrayInputStream);
                return (T) objectInputStream.readObject();
            } finally {
                if (objectInputStream != null) {
                    objectInputStream.close();
                }
                if (objectOutputStream != null) {
                    objectOutputStream.close();
                }
            }
        }
    

    相关文章

      网友评论

          本文标题:Java序列化

          本文链接:https://www.haomeiwen.com/subject/esufvctx.html