序列化和反序列化
序列化和反序列化
1、阿里巴巴规范
【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;
如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值
说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。
2、什么是序列化和反序列化
把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。
也就是说序列化是为了来传输数据的。
3、什么时候要序列化
当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化。
能够进行信息传输的地方。eg:发送给前端;发送给数据库;发送给redis等等;
4、序列化作用
当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构生成一个版本号ID。当执行反序列化时,程序会比较磁盘中的序列化版本号ID跟当前的类结构生成的版本号ID是否一致,如果一致则反序列化成功,否则,反序列化失败。加上版本号,有助于当我们的类结构发生了变化,依然可以与之前已经序列化的对象反序列化成功。
5、Serializable是什么
或许你会问,我在开发过程中,实体并没有实现序列化,但我同样可以将数据保存到mysql、Oracle数据库中,为什么非要序列化才能存储呢?
Serializable接口里面竟然什么都没有,只是个空接口。
看看官方的解答:
All subtypes of a serializable class are themselves
* serializable. The serialization interface has no methods or fields
* and serves only to identify the semantics of being serializable.
序列化接口没有方法和字段,仅仅用于识别类可被序列化的语义,仅仅只是一个标识而已。
当类来实现了这个接口的时候,表示的是在告诉JVM此类可被序列化,可被默认的序列化机制序列化。
Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。
所以Serializable接口仅仅只是一个标识,JVM在识别之后,会对其做特殊处理而已。
6、为什么要定义serialversionUID变量
实现了序列化接口之后,我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID。
看下一下对应的描述信息:
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected
翻译:
如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将根据类的各个方面计算该类的默认 serialVersionUID 值,如 Java(TM) 对象序列化规范中所述
如果没有显示序列化,那么默认生成一个。
但是上面也有推荐使用的方式。
看一下描述文字中的说明:
However, it is strongly recommended that all serializable
classes explicitly declare serialVersionUID values, since the default
serialVersionUID computation is highly sensitive to class details that
may vary depending on compiler implementations, and can thus result in
unexpectedInvalidClassExceptions during deserialization.
对应的翻译:
但是,强烈建议所有可序列化的
类显式声明 serialVersionUID 值,因为默认
serialVersionUID 计算对类细节高度敏感
可能因编译器实现而异,因此可能导致
反序列化期间出现意外InvalidClassExceptions。
7、示例
7.1、定义对象
public class User implements Serializable {
private Integer id;
private String message;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
7.2、编写序列化和反序列化方法
private static final String FILENAME = System.getProperty("user.dir")+"/aa.txt";
@Test
public void testOne(){
writer();
reader();
}
// 序列化
public static void writer(){
User user = new User();
user.setId(1);
user.setMessage("hello,serializable");
System.out.println("序列化之前的hashcode"+user.hashCode());
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(FILENAME)));){
objectOutputStream.writeObject(user);
System.out.println("序列化成功");
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
}
// 反序列化
public static User reader(){
User user = null;
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File(FILENAME)));){
user = (User) objectInputStream.readObject();
System.out.println("反序列化成功");
System.out.println("序列化之后的hashcode"+user.hashCode());
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
System.out.println("对应的user信息是:"+user.toString());
return user;
}
7.3、序列化传输媒介
序列化是将对象写到文件中来,反序列化是从文件中读取得到对象。利用文件作为传输媒介只是序列化和反序列化的一种方式而已。
这里选择的是利用文件。
7.4、序列化和反序列化后是不同对象
只需要将User写入到文件中,然后再从文件中进行恢复,恢复后得到的内容与之前完全一样,但是两者是不同的对象。
这个测试不要重写HashCode方法即可。
public class TestSerializable {
private static final String FILENAME = System.getProperty("user.dir")+"/aa.txt";
@Test
public void testOne(){
writer();
reader();
}
public static void writer(){
User user = new User();
user.setId(1);
user.setMessage("hello,serializable");
System.out.println("序列化之前的hashcode"+user.hashCode());
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(FILENAME)));){
objectOutputStream.writeObject(user);
System.out.println("序列化成功");
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
}
public static User reader(){
User user = null;
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File(FILENAME)));){
user = (User) objectInputStream.readObject();
System.out.println("反序列化成功");
System.out.println("序列化之后的hashcode"+user.hashCode());
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
System.out.println("对应的user信息是:"+user.toString());
return user;
}
}
对应的控制台输出:
序列化之前的hashcode355629945
序列化成功
反序列化成功
序列化之后的hashcode607635164
对应的user信息是:com.guang.bean.User@2437c6dc
7.4.1、指定和不指定serialVersionUID
如果类结构不发生任何变化,那么序列化和反序列化是没有任何问题的。但是只要类结构发生变化,那么反序列化的时候就会发生错误。
这里所谓的类结构通常被认为是属性字段。
7.4.1.1、不指定serialVersionUID
测试步骤:
- 1、先序列化对象,将对象写入到文件中;
- 2、修改类中的属性;
- 3、进行反序列化
如下所示:
对应的实体类:
public class User implements Serializable {
private Integer id;
private String message;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
序列化:
public class TestSerializable {
private static final String FILENAME = System.getProperty("user.dir")+"/aa.txt";
@Test
public void testOne(){
writer();
}
public static void writer(){
User user = new User();
user.setId(1);
user.setMessage("hello,serializable");
System.out.println("序列化之前的hashcode"+user.hashCode());
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(FILENAME)));){
objectOutputStream.writeObject(user);
System.out.println("序列化成功");
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
}
}
修改类的属性:
public class User implements Serializable {
private Integer id;
private String message;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
再进行反序列化:
public class TestSerializable {
private static final String FILENAME = System.getProperty("user.dir")+"/aa.txt";
@Test
public void testOne(){
reader();
}
public static User reader(){
User user = null;
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File(FILENAME)));){
user = (User) objectInputStream.readObject();
System.out.println("反序列化成功");
System.out.println("序列化之后的hashcode"+user.hashCode());
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
System.out.println("对应的user信息是:"+user.toString());
return user;
}
}
控制台报错:
序列化失败,message=com.guang.bean.User; local class incompatible: stream classdesc serialVersionUID = 3012992890419689086, local class serialVersionUID = -4927353852099075648
java.lang.NullPointerException
at com.guang.testserializable.TestSerializable.reader(TestSerializable.java:46)
at com.guang.testserializable.TestSerializable.testOne(TestSerializable.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
显示两个序列号不一致,空指针异常。
序列化和反序列化原理
这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误
7.4.2、指定serialVersionUID
对应的对象:
public class User implements Serializable {
private static final long serialVersionUID = -4927353852099075648L;
private Integer id;
private String message;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
进行序列化:
public class TestSerializable {
private static final String FILENAME = System.getProperty("user.dir")+"/aa.txt";
@Test
public void testOne(){
writer();
}
public static void writer(){
User user = new User();
user.setId(1);
user.setMessage("hello,serializable");
System.out.println("序列化之前的hashcode"+user.hashCode());
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(FILENAME)));){
objectOutputStream.writeObject(user);
System.out.println("序列化成功");
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
}
}
修改类的属性:
public class User implements Serializable {
private static final long serialVersionUID = -4927353852099075648L;
private Integer id;
private String message;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
进行反序列化:
public class TestSerializable {
private static final String FILENAME = System.getProperty("user.dir")+"/aa.txt";
@Test
public void testOne(){
reader();
}
public static User reader(){
User user = null;
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File(FILENAME)));){
user = (User) objectInputStream.readObject();
System.out.println("反序列化成功");
System.out.println("序列化之后的hashcode"+user.hashCode());
} catch (IOException e) {
System.out.println("序列化失败,message="+e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("序列化失败,message="+e.getMessage());
}
System.out.println("对应的user信息是:"+user.toString());
return user;
}
}
控制台显示:
反序列化成功
序列化之后的hashcode608188624
对应的user信息是:com.guang.bean.User@244038d0
7、自定义序列化工具类
上面是基于文件的,这里是基于内存的。
public class SerializeUtil {
public SerializeUtil() {
}
public static byte[] serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectOutputStream objectOutputStream = null;
byte[] var3;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.flush();
var3 = byteArrayOutputStream.toByteArray();
} finally {
IoUtil.close(objectOutputStream);
IoUtil.close(byteArrayOutputStream);
}
return var3;
}
public static <T> T deserialize(byte[] bytes) throws Exception {
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
Object var3;
try {
byteArrayInputStream = new ByteArrayInputStream(bytes);
objectInputStream = new ObjectInputStream(byteArrayInputStream);
var3 = objectInputStream.readObject();
} finally {
IoUtil.close(objectInputStream);
IoUtil.close(byteArrayInputStream);
}
return var3;
}
}
对应的IOUtils:
import java.io.Closeable;
public class IoUtil {
public IoUtil() {
}
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception var2) {
System.out.println("资源关闭失败!"+var2);
}
}
}
}
9、idea中指定生成serialVersionUID
参考https://blog.csdn.net/cm15835106905/article/details/124761727
10、应用
1、破坏单例;
2、避免浅克隆,进行深克隆;
11、总结
Serializable接口是一个标识,代表的是JVM会自动的给类进行特殊处理,如:加上序列化ID。
但是正是因为这个序列化id,可能导致会出现问题。如果是已经定义了序列化id,那么修改旧没有问题。如果没有定义,那么反序列化时,就会出现问题。
加入说十年前已经定义好了一个类,十年之后,对这个类进行了修改,那么再次进行反序列化时,就有可能出现问题。