什么是序列化
Java序列化就是将对象转换成不同形式存储,如二进制
序列化只会存储存储类的状态,而不会关注静态变量
序列化的本质是为了减少存储空间的使用
为什么需要序列化
因为Java是基于JVM之上,所以一个对象的生命周期不会比JVM的生命周期长,当我们需要保存一个对象的时候就需要序列化。
如何进行序列化
- 实现Serializable接口
- 拥有一个独立的序列化ID
- 使用ObjectInputStream和ObjectOutputStream进行对象的读写
关于序列化需要注意的一些点
- 如果不实现Serializable接口会抛出NoSerializableException异常
- 如果需要保存父类的变量,则父类也要实现Serializable接口
- JVM是否允许序列化不仅取决于类路径和功能代码是否一致,还取决于序列化ID是否相同,否则无法进行反序列化
- 序列化并不会保存静态变量
关于序列化ID的使用
使用Facade外观模式,进行检验。
定义Employee
public class Employee implements Serializable {
private String name;
private String address;
private transient int SSN;
private int number;
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", SSN=" + SSN +
", number=" + number +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getSSN() {
return SSN;
}
public void setSSN(int SSN) {
this.SSN = SSN;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
/**
* 序列化
* @param employee 对象
*/
public void ser(Employee employee){
try {
//用于存储序列化后的文件
FileOutputStream fileOutputStream = new FileOutputStream("employee.ser");
//Java序列化的API
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
outputStream.writeObject(employee);
outputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 反序列化
* @return employee
*/
public Employee deSer(){
Employee employee = null;
try {
//读取序列化之后的文件
FileInputStream fileInputStream = new FileInputStream("employee.ser");
//反序列化
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
employee = (Employee) inputStream.readObject();
inputStream.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return employee;
}
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("Alice");
employee.setAddress("nanjing");
employee.setNumber(1234567);
employee.setSSN(101);
TestSerializable serializable = new TestSerializable();
serializable.ser(employee);
Employee employee1 = serializable.deSer();
System.out.println(employee1.toString());
}
employee.ser文件中的内容,即序列化之后的结果
�� sr /com.cakemonster.javabasic.serializable.Employee�Of�&[6� I numberL addresst Ljava/lang/String;L nameq ~ xp ևt nanjingt Alice
运行结果
Employee{name='Alice', address='nanjing', SSN=0, number=1234567}
运行结果中的SSN是0,但是在main函数中employee.setSSN(101),因为在Employee类中使用了transient修饰了SSN这个变量。
transient关键字的作用
- 在某个类的变量前加上这个关键字可以阻止该变量序列化
- 在被反序列化之后,该变量会被赋予初值,如int 类型就是0,Integer就是null,即基本类型是初始值,对象就是null
常见的序列化方式
- Java原生的序列化方式 (就是上面实现的那种)
- XML&SOAP
- Json (目前最流行的方式)
- ProtocolBuffer (由谷歌提供的)
自定义序列化和反序列化策略
在类中覆盖writeObject()和readObject()方法。
如ArrayList类中就覆盖了这两个方法。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
在序列化和反序列化的过程中,JVM会视图调用对象类中的readObject()和writeObject()方法,进行自定义的序列化和反序列化,如果没有这样的方法,则会默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
调用readObject以及writeObject是在运行过程中使用反射进行调用的,所以没有显示的调用这两个方法进行序列化。
脑图
参考资料
IBM–Java序列化的高级认识