My Blog

对象序列化

本文源自:对象序列化为何要定义serialVersionUID的来龙去脉,因为原文不是对“序列化”的完整介绍,所以在此,结合个人理解,将“对象序列化”做一个简要梳理!

首先,为什么要序列化:

  1. 正常的Web项目中服务过程中,会产生”成百上千“的实例对象,而且随着用户访问量的增加,对象数据量可能会越来越多,例如:session等,那么如果这些对象都保存于服务内存中的话,再大的内存也有可能吃不消,因此,我们就需要将对象序列化到物理磁盘,需要的时候再反序列化回来。
  2. 我们知道对象本身在网络传输过程中是不可能直接传输的,需要进行转换为二进制文件,才能进行正常传送,这个转换过程,我们就称作序列化。

如何序列化

1.针对第一种磁盘序列化,我们需要对象实现Serializable接口,将对象以输出流的形式暂存在File文件中,当需要访问它的时候,再通过输入流写回内存。下面看一个示例:

我们创建一个User对象,只有一个name属性:

package cn.wxson.serializable;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
public class User implements Serializable {

    private String name;

    public User(String name) {
        this.name = name;
    }
}

我们创建一个User对象outUser,并把它序列化到文件user,再将user文件中的内容反序列化到内存inUser对象中。

package cn.wxson.serializable;

import lombok.extern.slf4j.Slf4j;

import java.io.*;

@Slf4j
public class SerializableTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 将对象序列化到文件
        User outUser = new User("Tom");
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("user"));
        oo.writeObject(outUser);
        oo.close();

        // 将文件反序列化为对象
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream("user"));
        User inUser = (User) oi.readObject();
        log.info("Name:" + inUser.getName());
        oi.close();
    }
}

2.同样的道理,网络传输中的对象序列化,也要实现Serialiable接口,程序内部会自动将对象以字节流形式写入磁盘,然后通过网络通信读取磁盘信息到内存进行传输。当然,写入磁盘的做法是标准IO的做法,那么NIO相较于标准IO传输,能够更快的原因是,它的传输过程不需要对象落地,完全利用系统的虚拟内存技术,从Buffer缓冲区直接将序列化后的对象传输到网络,所以它的传输效率更高。

serialVersionUID的含义

我们通常在写代码时,都会为实现Serialiable接口的对象增加serialVersionUID属性。那么,这个serialVersionUID又代表什么含义呢?还是以上一个示例来解释一下!

我们看到刚才的示例中,User对象并没有serialVersionUID值。那么,我们执行“将对象序列化到user文件”后,在User对象中新增一个age属性:

package cn.wxson.serializable;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
public class User implements Serializable {

    private String name;
    private int age;

    public User(String name) {
        this.name = name;
    }
}

我们再来执行“将文件反序列化为对象”,这时就会出现以下错误:

Exception in thread "main" java.io.InvalidClassException: cn.wxson.serializable.User; local class incompatible: stream classdesc serialVersionUID = 4797623867994513725, local class serialVersionUID = -6015633555568763825
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at cn.wxson.serializable.SerializableTest.main(SerializableTest.java:19)

那么,为什么会报错呢?

首先,我们先来看一下报错信息,它的大致意思是,接收对象User中的serialVersionUID与本地文件user中的serialVersionUID不一致导致了该错误。但是,我们没有给User对象和文件user中标识serialVersionUID啊?

其实,虽然我们没有给User对象指定serialVersionUID属性,但Java编译器对User对象outUser进行序列化时,已经自动给它通过摘要算法(类似于指纹算法)生成了serialVersionUID,存储在user文件中,摘要算法精度很高,这个serialVersionUID值是唯一的,而在我们在为对象新增age属性后,User对象就有了一个新的serialVersionUID值,所以,利用新的serialVersionUID值来反序列化是失败的,接收不到序列化对象。

正确做法:我们为User对象指定一个固定serialVersionUID值。

private static final long serialVersionUID = -263618247375550128L;

同样,重复上面的步骤,先对只有name属性的User对象inUser执行序列化操作,然后,在User中增加age属性后,执行反序列化操作,这次就是成功的了!

希望通过本文学习,加深你对序列化的理解!

posted @ 2020-08-07 21:21  王心森  阅读(292)  评论(0编辑  收藏  举报