对象序列化
本文源自:对象序列化为何要定义serialVersionUID的来龙去脉,因为原文不是对“序列化”的完整介绍,所以在此,结合个人理解,将“对象序列化”做一个简要梳理!
首先,为什么要序列化:
- 正常的Web项目中服务过程中,会产生”成百上千“的实例对象,而且随着用户访问量的增加,对象数据量可能会越来越多,例如:session等,那么如果这些对象都保存于服务内存中的话,再大的内存也有可能吃不消,因此,我们就需要将对象序列化到物理磁盘,需要的时候再反序列化回来。
- 我们知道对象本身在网络传输过程中是不可能直接传输的,需要进行转换为二进制文件,才能进行正常传送,这个转换过程,我们就称作序列化。
如何序列化
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
属性后,执行反序列化操作,这次就是成功的了!
希望通过本文学习,加深你对序列化的理解!
个人网址:http://wxson.cn(待开通)
-----------------------------------------------------------