文章目录
序列化和反序列化
序列化是指将对象的状态信息转化为可传输或存储的形式的过程,一般转化为二进制流或者xml形式,与序列化相对应的过程称之为反序列化,它是指将二进制流或者xml形式转化为对象的过程。
序列化的作用
- 对象信息的持久化存储;
- 对象信息的网络传输;
- 使程序更具有可维护性?
- 可以弥补操作系统,语言之前的差异性;
序列化格式
1.二进制字节;
2.xml格式;
3.json格式;
JAVA的序列化
序列化接口——Serializable
java.io.Serializable接口(标记接口,不包括任何方法):JVM会将实现了Serializable接口的对象转化为字节序列,并能够将这个字节序列完全恢复为原来的对象;
序列化与反序列化简单的实现:
public class Index {
@Test
public void serializeObj() throws IOException {
PersonEntity entity = new PersonEntity();
entity.setName("Bob");
FileOutputStream fileInputStream = new FileOutputStream("E://temp/a.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
objectOutputStream.writeObject(entity);
objectOutputStream.close();
fileInputStream.close();
}
@Test
public void deserializeObj() throws IOException, ClassNotFoundException {
PersonEntity entity = new PersonEntity();
FileInputStream fileInputStream = new FileInputStream("E://temp/a.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
entity = (PersonEntity) objectInputStream.readObject();
System.out.println("entity = " + entity);
objectInputStream.close();
fileInputStream.close();
}
}
package com.basic.practice.serialize;
import java.io.Serializable;
import java.util.Objects;
import java.util.StringJoiner;
public class PersonEntity implements Serializable {
private static final long serialVersionUID = 865294760793762612L;
private String name;
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new StringJoiner(", ", PersonEntity.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("age=" + age)
.toString();
}
}
对象(包括String、Integer等包装类)实现SerializaSeble接口,根据某个流创建ObjectOutputStream对象,调用writeObject就可以进行序列化;
反序列化特点:对SerializaSeble对象进行还原的过程中,没有调用任何构造器(包括默认构造器),对象是通过从InputStream中取二进制位恢复过来的;
注意:
①int等基础数据类型也可以进行序列化;
②可序列化类的子类型都是可序列化的;
③序列化的类有父类型,想要序列化父类中的成员,父类需要实现SerializaSeble接口;
④static修饰的成员变量不会被序列化;
序列化版本ID——serialVersionUID
serialVersionUID:JAVA序列化机制在运行时根据判断类的serialVersionUID,来验证版本是否一致;JVM会把传来的字节流中的serialVersionUID和本地相应实体的serialVersionUID进行相应比较,如果相同,就认为版本一致,可以进行反序列化,否则不能进行反序列化,并抛出序列化ID不一致的异常(java.io.InvalidClassException);
凡是实现了SerializaSeble接口接口的类,都有一个long类型的静态变量-序列化版本ID;如果没有显示的声明这个serialVersionUID变量,java序列化机制会根据编译的class文件自动生成一个serialVersionUID,只有同一次编译的class才会生成相同的serialVersionUID;
应用场景:
①在某些场景下,希望类的不同版本对于序列化兼容,因此要确保不同版本类的serialVersionUID序列化一致,例如:dubbo版本的服务升级时,api中的实体升级兼容;
②在某些场景下,不希望类的不同版本对于序列化兼容,因此要确保不同版本类的serialVersionUID序列化不一致;
实现类的部分属性序列化的方法
transient关键字
类实现Serializable接口,并用transient关键字修饰不需要序列化的成员变量;
transient关键字仅修饰变量,不修饰类和方法;
序列化时,类实例中被transient关键字修饰的成员变量的序列化;反序列化时,被transient关键字修饰的成员变量不会被持久化和恢复;
实现Externalizable接口
通过实现java.io.Externalizable接口进行序列化,该接口继承了SerializaSeble接口,并且添加了2个方法writeExternal()和readExternal(),方法会在序列化和反序列化的过程中分别自动被调用,以便执行特殊操作;
实现Externalizable接口对象在恢复过程中会调用默认构造方法(默认构造方法必须public,没有对应默认构造方法时,反序列化会抛出错误java.io.OptionalDataException),然后调用readExternal() 接口;
注意:
①writeExternal(ObjectOutput out)方法中序列化成员属性时,使用的write方法需要和readExternal(ObjectInput in)方法中反序列化时使用的read方法一一对应,例如:使用out.writeInt(age)进行序列化Integer类型的age,就必须使用in.readInt()来进行反序列化恢复age,而不能使用(Integer) in.readObject()来恢复,否则会抛出异常(java.io.OptionalDataException);
②readExternal(ObjectInput in)在反序列化时,反序列化恢复成员的顺序和成员属性的数量,必须和writeExternal(ObjectOutput out)方法序列化成员时保持一致,否则会抛出异常(java.io.EOFException)
package com.basic.practice.serialize;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* @auther
* @date 7/5/2020 23:54
* @description
**/
public class Man implements Externalizable {
private String name;
private String address;
private Integer age;
public Man() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String)in.readObject();
this.age = in.readInt();
}
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 Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Man{");
sb.append("name='").append(name).append('\'');
sb.append(", address='").append(address).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}
添加writeObject()和readObject()方法
类实现Serializable接口,并且向类中添加writeObject()和readObject()方法,这两个方法必须是private和void修饰;
原理:JAVA调用ObjectOutputStream类序列化时,会检查类其是否有私有的,且无返回值的writeObject方法,如果有,其会委托该方法进行对象序列化,源码如下:
ObjectStreamClass.java
...
...
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
...
...
package com.basic.practice.serialize;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @auther
* @date 7/5/2020 23:40
* @description
**/
public class People implements Serializable {
private String firstName;
private int age;
private String birthDay;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.writeObject(firstName);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
this.firstName = (String) ois.readObject();
}
public String getBirthDay() {
return birthDay;
}
public void setBirthDay(String birthDay) {
this.birthDay = birthDay;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("People{");
sb.append("firstName='").append(firstName).append('\'');
sb.append(", age=").append(age);
sb.append(", birthDay='").append(birthDay).append('\'');
sb.append('}');
return sb.toString();
}
}