Java序列化与反序列化
什么是序列化和反序列化
- 序列化:将java对象以一连串的字节保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。
- 反序列化:将保存在磁盘文件中的java字节码重新转换成java对象称为反序列化。
- 两个过程配合起来能够完成内存中对象的传输和保存
序列化的重要作用
在传递和保存对象时,保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。java是面向对象的开发方式,一切都是java对象,想要在网络中传输java对象,可以使用序列化和反序列化去实现,发送发需要将java对象转换为字节序列,然后在网络上传送,接收方收到字符序列后,会通过反序列化将字节序列恢复成java对象。
核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流中所保存的对象状态及描述信息)
为什么需要设置SerializableVersionUID
如果不显示指定serialVersionUID, JVM在序列化时会根据属性自动生成一个serialVersionUID, 然后与属性一起序列化, 再进行持久化或网络传输. 在反序列化时, JVM会再根据属性自动生成一个新版serialVersionUID, 然后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比较, 如果相同则反序列化成功, 否则报错.
如果显示指定了serialVersionUID, JVM在序列化和反序列化时仍然都会生成一个serialVersionUID, 但值为我们显示指定的值, 这样在反序列化时新旧版本的serialVersionUID就一致了.
在实际开发中, 不显示指定serialVersionUID的情况会导致什么问题? 如果我们的类写完后不再修改, 那当然不会有问题, 但这在实际开发中是不可能的, 我们的类会不断迭代, 一旦类被修改了, 那旧对象反序列化就会报错. 所以在实际开发中, 我们都会显示指定一个serialVersionUID, 值是多少无所谓, 只要不变就行。
序列化和反序列化的实现
JDK类库提供的序列化API
Java在序列化时一个对象,将会调用这个对象中的 writeObject
方法,参数类型是
ObjectOutputStream
,开发者可以将任何内容写入这个stream中;反序列化时,会调用
readObject
,开发者也可以从中读取出前面写入的内容,并进行处理。
java.io.ObjectOutputStream
- 表示对象输出流,其中writeObject(Object obj)方法可以将给定参数的obj对象进行序列化,将转换的一连串的字节序列写到指定的目标输出流中。
java.io.ObjectInputStream
- 该类表示对象输入流,该类下的readObject(Object obj)方法会从源输入流中读取字节序列,并将它反序列化为一个java对象并返回。
序列化要求:
实现序列化的类对象必须实现了Serializable类或Externalizable类才能被序列化,否则会抛出异常。
实现java序列化和反序列化的三种方法:
现在要对student类进行序列化和反序列化,遵循以下方法:
方法一:若student类实现了serializable接口,则可以通过objectOutputstream和objectinputstream默认的序列化和反序列化方式,对非transient的实例变量进行序列化和反序列化。
方法二:若student类实现了serializable接口,并且定义了writeObject(objectOutputStream out)和
readObject(objectinputStream in)方法,则可以直接调用student类的两种方法进行序列化和反序列化
方法三:若student类实现了Externalizable接口,则必须实现readExternal(Objectinput in)和writeExternal(Objectoutput out)方法进行序列化和反序列化。
JDK类库中的序列化步骤:
第一步:创建一个输出流对象,它可以包装一个输出流对象,如:文件输出流。
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream("E:\\StudentTest.txt"));
//对象序列化:使用对象字节输出流包装字节输出流管道
第二步:通过输出流对象的writeObject()方法写对象
out.writeObject("hollo word");
out.writeObject("happy")
JDK中反序列化操作:
第一步:创建文件输入流对象
ObjectInputStream in = new ObjectInputStream(new fileInputStream("E:\\StudentTest.txt"));
第二步:调用readObject()方法
String obj1 = (String)in.readObject();
String obj2 = (String)in.readObject();
为了保证正确读取数据,对象输出流写入对象的顺序与对象输入流读取对象的顺序一致。
演示示例
首先创建一个继承了serializable类的student类
import java.io.Serializable;
/**
对象如果要序列化,必须实现Serializable序列化接口。
*/
public class Student implements Serializable {
// 申明序列化的版本号码
// 序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
private String name;
private String loginName;
// transient修饰的成员变量不参与序列化
private transient String passWord;
private int age ;
public Student(){
}
public Student(String name, String loginName, String passWord, int age) {
this.name = name;
this.loginName = loginName;
this.passWord = passWord;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", loginName='" + loginName + '\'' +
", passWord='" + passWord + '\'' +
", age=" + age +
'}';
}
}
把Student类的对象序列化到txt文件(D:Student1.txt)中,并对文件进行反序列化:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
//进行序列化
// 1、创建学生对象
Student s = new Student("张三", "zhangsan","123456", 21);
// 2、对象序列化:使用对象字节输出流包装字节输出流管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/StudentTest.txt"));
// 3、直接调用序列化方法
oos.writeObject(s);
// 4、释放资源
oos.close();
System.out.println("序列化完成了~~");
//进行反序列化
// 1、创建对象字节输入流管道包装低级的字节输入流管道
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:/StudentTest.txt"));
// 2、调用对象字节输入流的反序列化方法
Student s1 = (Student) is.readObject();
System.out.println(s1);//调用s1对象的toString方法输出对象内容
System.out.println("反序列化完成");
}
}
自定义反序列化
Java中,序列化的过程是允许开发者参与的,我们可以通过重写readObject和writeObject方法来进行
import java.io.Serializable;
/**
对象如果要序列化,必须实现Serializable序列化接口。
*/
public class Student3 implements Serializable {
// 申明序列化的版本号码
// 序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
private String name;
private String loginName;
// transient修饰的成员变量不参与序列化
private transient String passWord;
private int age ;
public Student3(){
}
public Student3(String name, String loginName, String passWord, int age) {
this.name = name;
this.loginName = loginName;
this.passWord = passWord;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLoginName() { return loginName; }
public void setLoginName(String loginName) { this.loginName = loginName; }
public String getPassWord() { return passWord; }
public void setPassWord(String passWord) { this.passWord = passWord; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
s.defaultWriteObject();
this.age = 999;
s.writeObject(this.age);
}
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException{
s.defaultReadObject();
this.age = (int)s.readObject();
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", loginName='" + loginName + '\'' +
", passWord='" + passWord + '\'' +
", age=" + age +
'}';
}
}
另一个序列化接口 Externalizable
Student2类
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Student2 implements Externalizable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("name", name)
.append("age", age)
.toString();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
进行序列化以及反序列化
public class SerializableDemo1 {
public static void main(String[] args) throws Exception {
//初始化对象
Student2 user = new Student2();
user.setName("alexsel");
user.setAge(23);
System.out.println(user);
//序列化对象到文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/Student2Test.txt"));
oos.writeObject(user);
oos.close();
System.out.println("序列化完成");
//反序列化
File file = new File("D:/Student2Test.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Student2 newUser = (Student2)ois.readObject();
System.out.println(newUser);
ois.close();
System.out.println("反序列化完成");
}
}
输出结果
根据输出的结果可以看到,对Student2进行序列化然后再反序列化之后对象的属性都恢复成了默认值,即之前那个对象的状态没有被持久保存下来,这就是Externalization和Serialization接口的区别,其中前者接口会被恢复成为默认值,后者接口不会恢复默认值。
如果需要恢复,这里就需要重写两个抽象方法,分别是writeExternal与readExternal两个抽象方法。
修改过后的Student2类
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Student2 implements Externalizable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("name", name)
.append("age", age)
.toString();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String)in.readObject();
age = in.readInt();
}
}
我们再次运行刚才序列化以及反序列化的代码,反写属性已经成功被读取
静态变量的序列化
public class Test1 implements Serializable {
private static final long serialVersionUID = 1L;
public static int staticVar = 5;
public static void main(String[] args) {
try {
//初始时staticVar为5
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close();
//序列化后修改为10
Test.staticVar = 10;
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t = (Test) oin.readObject();
oin.close();
//再读取,通过t.staticVar打印新的值
System.out.println(t.staticVar);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果
代码阐述一下过程,在main方法中,对象序列化以后,修改静态变量的数值,再把序列化后的对象读取出来,此时输出的值为10. 理解如下:打印的staticVar是从读取对象里获得的,打印10的原因是因为序列化时,不保存静态变量,只保存内存中的状态。此时修改静态变量的值,修改的是类中的值,输出的也是类中的值,和内存无关。
Transient关键字
Transient关键字,加上以后,可以阻止该变量被序列化到文件中,反序列化以后,变量的值设定为初始值。