首页 > 代码库 > Java 序列化与反序列化
Java 序列化与反序列化
- 序列化与反序列化的概念
把对象转换为字节序列的过程称为对象的序列化;将字节序列恢复为对象的过程称为反序列化。
使用场景:把对象的序列保存到硬盘上,通常放在一个文件中;网络上传送对象的文件序列。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
- 序列化接口
Java对象,实现了Serializable和Externalizable接口就可以实现序列化,Externalizable接口继承自Serializable接口,实现externalizable的对象完全由自身控制序列化的行为,而实现Serializable接口的对象则采用默认的序列化方式。
java.io.ObjectOutputStream代表对象输出流,可以用writeObject(Object obj)方法将对象序列化之后,将得到的字节序列写到一个目标输出流中;
java.io.ObjectInputStream代表对象输入流,可以用readObject()从一个源中读取序列,再将其反序列化为一个对象。
- 序列化与反序列化步骤
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
首先,创建一个对象:
import java.io.Serializable;public class Person implements Serializable { /** * */ private static final long serialVersionUID = 1L; private int age; private String name; private String sex; private int height; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + ", sex=" + sex + ", height=" + height + "]"; }}
序列化与反序列化:
import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import com.changjiang.test.testFuction.entity.Person;public class TestSerializable { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { serialize(); deSerializePerson(); } private static void serialize() throws FileNotFoundException, IOException { Person p = new Person(); p.setAge(15); p.setName("Game"); p.setHeight(178); p.setSex("male"); ObjectOutputStream ops = new ObjectOutputStream(new FileOutputStream(new File("E:/logs/person.txt"))); ops.writeObject(p); System.out.println("已完成对象Person序列化,并将序列化结果输出到文件"); ops.close(); } private static void deSerializePerson() throws FileNotFoundException, IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/logs/person.txt"))); Person p = (Person) ois.readObject(); System.out.println("已完成对象Person的反序列化,并将反序列化的结果输出到控制台:" + p); ois.close(); }}
最后的输出结果:
已完成对象Person序列化,并将序列化结果输出到文件已完成对象Person的反序列化,并将反序列化的结果输出到控制台:Person [age=15, name=Game, sex=male, height=178]
而输出文件中的字节内容:
sr -com.changjiang.test.testFuction.entity.Person I ageI heightL namet Ljava/lang/String;L sexq ~ xp 瞭 Gamet male
- serialVersionUID
实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现如下的警告提示:
The serializable class Person does not declare a static final serialVersionUID field of type long
编译器会自动提示以下方案来解决:
选择前者即是用了默认的方法来生成该ID(1L),而后者则是用类名,接口名,方法和属性等来生成的一个long值。
它究竟有什么作用呢?
在之前的例子中,我们在Person类中加入了一句:
private static final long serialVersionUID = 1L;
并且在TestSerializable这个类中将Person对象序列化进了一个文本文件,如果此时我们修改了这个ID的值,那么反序列化就会得到异常信息:
Exception in thread "main" java.io.InvalidClassException: com.changjiang.test.testFuction.entity.Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) at com.changjiang.test.testFuction.test.TestSerializable.deSerializePerson(TestSerializable.java:34) at com.changjiang.test.testFuction.test.TestSerializable.main(TestSerializable.java:17)
这就是说,序列化的结果是根据ID=1L计算得出的,当修改了这个值之后,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。所以,想要修改已经序列化的类之后再反序列化之前的内容,只要前后的ID一致即可。
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。如果不对该值显示定义出来,类的serialVersionUID的默认值则完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。所以,简单地说,希望兼容,则显示定义一致的ID,希望不兼容,则显示定义不一致的ID,为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化的类中对其赋予明确的值。
Java 序列化与反序列化