Java序列化
1 Java序列化与反序列化
- Java序列化:把 Java对象 转换为字节序列的过程,该字节序列包括该对象的数据、有关对象类型的信息和存储在对象中数据的类型,便于保存在内存、文件和数据库中
- Java反序列化:把字节序列恢复为 Java对象 的过程,序列化对象写入文件后, 可以从文件读取出来并对它反序列化
- Java序列化过程是 JVM 独立的,即在一个平台上序列化的对象可以在另一个完全不同的平台反序列化该对象
ObjectInputStream
和ObjectOutputStream
包含反序列化对象和序列化对象的方法
2 Java序列化应用场景
序列化和反序列化是让 Java对象 脱离 Java环境 的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。
- HTTP: 多平台间通信和管理
- RMI: Java的一组支持开发分布式应用程序的API,实现了不同操作系统间程序方法的调用。RMI的传输100%基于反序列化
- JMX: 是一套标准的代理和服务,用户可以在任何 Java应用程序 中使用这些代理和服务实现管理
3 Java序列化过程
序列化对象Person类:
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = 2545528385L; //序列号
private String name;
private int age;
private String address;
private transient int SSN; // transient变量
public static int staticVariable = 1; // 静态变量
public Person() {
}
public Person(String name, int age, String address, int sSN) {
this.name = name;
this.age = age;
this.address = address;
SSN = sSN;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getSSN() {
return SSN;
}
public void setSSN(int sSN) {
SSN = sSN;
}
public static int getStaticVariable() {
return staticVariable;
}
public static void setStaticVariable(int staticVariable) {
Person.staticVariable = staticVariable;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "SerialVersionUID: " + serialVersionUID + "\n" +
"Name: " + this.name + "\n" +
"Age: " + this.age + "\n" +
"Address: " + this.address + "\n" +
"SSN: " + SSN + "\n" +
"Version: " + staticVariable + "\n";
}
}
序列化测试代码:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Serialize {
public static void main(String[] args) {
/**
* 序列化
*/
Person person = new Person();
person.setName("Alice");
person.setAge(20);
person.setAddress("Changsha");
person.setSSN(123456);
System.out.println("========== 序列化前输出 ========");
System.out.println(person.toString());
try {
FileOutputStream fileOut = new FileOutputStream("./serialize.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
} catch (Exception e) {
//TODO: handle exception
e.printStackTrace();
}
Person obj = null;
/**
* 反序列化前改变JVM中静态变量的值
*/
Person.setStaticVariable(100);
try {
FileInputStream fileIn = new FileInputStream("./serialize.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
obj = (Person) in.readObject();
in.close();
fileIn.close();
} catch (Exception e) {
//TODO: handle exception
e.printStackTrace();
}
System.out.println("========= 序列化后输出 ==========");
System.out.println(obj.toString());
}
}
输出结果:
========== 序列化前输出 ========
SerialVersionUID: 2545528385
Name: Alice
Age: 20
Address: Changsha
SSN: 123456
Version: 1
========= 序列化后输出 ==========
SerialVersionUID: 2545528385
Name: Alice
Age: 20
Address: Changsha
SSN: 0
Version: 100
4 Java序列化过程详解
4.1 Java序列化ID的作用
序列化ID即
serialVersionUID
在 Java序列化 过程中起着关键的作用,它决定着反序列化是否能成功。
Java的序列化机制在运行时判断类的 serialVersionUID
来验证版本的一致性。在进行反序列化时,JVM会把字节流中的 serialVersionUID
与当前 Class 中 serialVersionUID
进行比较,如果相同则认为是一致的,可以进行反序列化;否则反序列化失败,捕获异常。
如果在类中没有显示的定义 serialVersionUID
,则每次编译则自动生成一个随机 serialVersionUID
标识。这样会导致一个问题,每次编译后的序列号都不同,导致反序列化失败 (如字节流文件中使用的是第一次编译后的数据,然后在测试时又去编译一次Person类,导致两者的序列号不一致)。
建议做法:对需要序列化的类显示定义 serialVersionUID
4.2 transient的作用
一个类实现了
Serilizable
接口即可被序列化,即这个类的属性和方法均会被自动序列化。
从 SSN
属性可以看出,被 transient
修饰的属性不会序列化到字节流中,而在反序列化时该属性就赋默认值。
transient关键字:将不需要序列化的属性前添加 transient 关键字,在序列化对象的时候就不会序列化该属性。
4.3 静态变量
从 Version
属性可以看出,静态变量的值不是序列化到字节流中的值,而是当前 JVM 中的值。
结论:静态变量不能被序列化,但 serialVersionUID
除外,该变量会在序列化时存储到字节流中。
5 参考文章
[1] Java 序列化
[3] java 序列化ID的作用
[4] 静态变量能被序列化吗?