浅谈java序列化
在谈及java序列化之前,先给大家举一个比较实用的例子;
小明准备出国留学,但是随身携带的都是人民币,到了国外后,发现人民币好像不太好使了,那边只用当前国家规定的法币或者美元,没有办法,小明只好找到一家银行,将人民币兑换成当地货币或者美元,才能保证有钱花。
没有规矩,不成方圆。这个理儿,在java中也是一样的存在。首先,我们先来了解一下什么是序列化。
把对象转换为字节序列的过程称为对象的序列化
。
把字节序列恢复为对象的过程称为对象的反序列化
。
有序列化,自然就有反序列化,就好比将人民币兑换成美元,同样可以将美元兑换成人民币。在java中,将对象序列化主要有两种用途。
一种是将对象的字节序列存储到磁盘中存储,另外一种则是在网络中进行数据传输。
JDK为我们提供了一种序列化方式,我们可以通过JDK的序列化将一个对象转换成字节序列存储到磁盘中,也可以将对象序列化成字节序列用户网络数据传输。下面,我们先来看一下将一个对象存储到本地磁盘没有进行系列化会是怎样的。
import lombok.Data;
* 对象信息:测试为序列化情况下
@Data
public class User {
private String name;
private String address;
private Integer age;
}
测试代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test02 {
* 测试java序列化
* @param args
public static void main(String[] args) {
User user = new User();
user.setName("加耀");
user.setAddress("湖北武汉");
user.setAge(24);
String localAddress = "C:\\Users\\Administrator\\Desktop\\user.txt";
try {
// 测试将对象写入本地磁盘
FileOutputStream fileOut = new FileOutputStream(localAddress);
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(user);
out.close();
fileOut.close();
System.out.println("对象文件保存成功,储存位置:" + localAddress);
// 测试将本地磁盘中的对象字节序列读取储存到对象中
FileInputStream fileInput = new FileInputStream(localAddress);
ObjectInputStream input = new ObjectInputStream(fileInput);
user = (User) input.readObject();
System.out.println(user);
System.out.println("成功将对象字节反序列化");
} catch (Exception e) {
e.printStackTrace();
}
通过运行测试代码,在运行中代码出错。错误信息如下:
java.io.NotSerializableException: User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at Test02.main(Test02.java:22)
通过错误日志可以看出,在代码的第22行,将对象写入到磁盘文件时出错。从报错信息的第一行可以看得出,没有序列化异常,对象User。所以,当对象没有序列化时,是无法写入到本地磁盘的。那下面,我们尝试将对象序列化一下,然后重新运行一下上述代码。
import lombok.Data;
import java.io.Serializable;
* 对象信息:测试为序列化情况下
@Data
public class User implements Serializable {
private String name;
private String address;
private Integer age;
}
将上面的对象进行改造后,即 实现Serializable接口。
然后重新运行一下上面的代码:
对象文件保存成功,储存位置:C:\Users\Administrator\Desktop\user.txt
User(name=加耀, address=湖北武汉, age=24)
成功将对象字节反序列化
可以见得,当对象实现了Serializable接口后,对象信息就可以成功的写入到本地磁盘并且可以从本地磁盘中顺利读取到。我们再看一下本地电脑桌面上新建的这个user.txt文本,打开看看。
对象实现序列化接口可以完美的解决这种问题,但是,上述代码并不完美。此时User对象中只有3个字段,name、address、age;如果再添加一个字段后,然后重新运行一下代码,然后再删除一个字段,此时代码还能顺利运行吗?
在User对象中添加一个sex字段。
private String sex;
然后重新运行一下代码,然后删除User中的age字段,并且注释掉代码中写入本地磁盘的代码,只保留读取的方法,此时代码还能运行吗?
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test02 {
* 测试java序列化
* @param args
public static void main(String[] args) {
User user = new User();
user.setName("加耀");
user.setAddress("湖北武汉");
// user.setAge(24);
String localAddress = "C:\\Users\\Administrator\\Desktop\\user.txt";
try {
// 测试将对象写入本地磁盘
// FileOutputStream fileOut = new FileOutputStream(localAddress);
// ObjectOutputStream out = new ObjectOutputStream(fileOut);
// out.writeObject(user);
// out.close();
// fileOut.close();
// System.out.println("对象文件保存成功,储存位置:" + localAddress);
// 测试将本地磁盘中的对象字节序列读取储存到对象中
FileInputStream fileInput = new FileInputStream(localAddress);
ObjectInputStream input = new ObjectInputStream(fileInput);
user = (User) input.readObject();
System.out.println(user);
System.out.println("成功将对象字节反序列化");
} catch (Exception e) {
e.printStackTrace();
}
此时,再运行代码,出现了下列异常:
java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -5370401598153958555, local class serialVersionUID = -5049882203261325612
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at Test02.main(Test02.java:29)
通过报错信息可以看的出来,log日志中指出,读取本地磁盘文件时所采用的序列化标识为serialVersionUID = -5370401598153958555,但是本地文件的序列化标识是serialVersionUID = -5049882203261325612。
读取和写入采用的是不同的系列化标志,导致出错。那么,怎样避免这种情况呢?
让我们回到最开始。
前面,我们将对象实现了序列化接口Serializable,但是我们并没有指定对象的序列化方式(或序列化标识)。我们将上面的对象再进行改造一次。
import lombok.Data;
import java.io.Serializable;
* 对象信息:测试为序列化情况下
@Data
public class User implements Serializable {
// 指定其序列化的UID
private static final long serialVersionUID = 1L;
private String name;