序列化
将数据结构或对象转换为二进制串的过程
Serializable接口
Java提供的序列化接口
public interface Serializable {
}
Serializable本身没有什么需要实现的方法,只是用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。
使用
类定义
public class Student implements Serializable {
//serialVersionUID唯一标识了一个可序列化的类
private static final long serialVersionUID = -2100492893943893602L;
private String name;
private String sax;
private Integer age;
//Course也需要实现Serializable接口
private List<Course> courses;
//用transient关键字标记的成员变量不参与序列化(在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null)
private transient Date createTime;
//静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的“状态”,也就是它的成员变量,因此序列化不会关注静态变量)
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
public Student() {
System.out.println("Student: empty");
}
public Student(String name, String sax, Integer age) {
System.out.println("Student: " + name + " " + sax + " " + age);
this.name = name;
this.sax = sax;
this.age = age;
}
...
}
////Course也需要实现Serializable接口
public class Course implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
}
序列化
synchronized public static boolean saveObject(Object obj, String path) {
if (obj == null) {
return false;
}
ObjectOutputStream oos = null;
try {
// 创建序列化流对象
oos = new ObjectOutputStream(new FileOutputStream(path));
//序列化
oos.writeObject(obj);
oos.close();
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
// 释放资源
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
反序列化
synchronized public static <T> T readObject(String path) {
ObjectInputStream ojs = null;
try {
// 创建反序列化对象
ojs = new ObjectInputStream(new FileInputStream(path));
// 还原对象
return (T) ojs.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ojs!=null){
try {
// 释放资源
ojs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
serialVersionUID
serialVersionUID 用来表明类的不同版本间的兼容性,最好显式定义该属性(否则jvm会自行计算,在不同jvm上反序列化时可能会计算出不同的结果,导致无法正确反序列化)
序列化过程
序列化的过程就在ObjectOutputStream类的writeObject函数里
主要流程概述
- 写入魔数(Magic Number)
- 写入本类的描述信息
- 向上递归写入各个parent类的描述信息
- 从最终父类向下递归写入各个类中的基本类型的字段的数据
- 写入对象类型的成员的所属类的描述信息(比如有个成员变量是MyClass obj; 那么就会写入MyClass类的描述信息)
writeObject流程
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
//如果继承了ObjectOutputStream,可以重写writeObjectOverride方法来实现的新的序列化逻辑
//注意,writeObject是final的,无法被重写
writeObjectOverride(obj);
return;
}
try {
//一般情况下直接调用这个方法
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// handle previously written and non-replaceable objects
//处理几种特殊的对象,先跳过
int h;
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
//创建描述类的信息
//注意,这个desc里包含了各个父类的信息
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
if (enableReplace) {
//如果有代理(这部分先跳过)
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
}
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
//重点!如果实现了Serializable接口就调用writeOrdinaryObject写入信息
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
//删除了一些日志信息
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
...
try {
desc.checkSerialize();
//写入Object标志位
bout.writeByte(TC_OBJECT);
//写入类的描述信息
//注意,这里会向上递归写入各个父类的信息
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
//如果实现了Externalizable接口
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
...
}
}
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
//这个数组,从最终父类开始往下按顺序放(即实现了向下递归,先写入最终父类的实例数据)
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
//如果实现了writeObject()方法,调用该方法写入实例数据
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
...
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
...
}
curPut = oldPut;
} else {
// 调用默认的方法写入实例数据
defaultWriteFields(obj, slotDesc);
}
}
}
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
//写入基本数据类型的数据
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
//遍历对象类型的字段
for (int i = 0; i < objVals.length; i++) {
...
try {
//这里又调用了writeObject0,即列化对象类型的字段
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
...
}
}
}
注意事项
-
未实现Serializable 的属性状态无法被序列化,在反序列化过程中,对应的非可序列化字段将调用无参构造函数重新创建,因此这个字段所属的类必须有无参构造函数
-
一个实现序列化的类,他的子类也是可序列化的
-
如果子类实现了序列化接口,父类没有实现Serializable接口,那么必须为父类提供无参构造函数
-
反序列化时,如果原来的类新添加了一个字段,该字段会被初始化为默认值,少一个字段也不会出现问题
-
枚举类型序列化时只会保存名字,也就是值可能会改变,比如
public enum Num { TWO, ONE, THREE; public void printValues() { System.out.println(ONE + " ONE.ordinal " + ONE.ordinal()); System.out.println(TWO + " TWO.ordinal " + TWO.ordinal()); System.out.println(THREE + " THREE.ordinal " + THREE.ordinal()); } public static void testSerializable() throws Exception { File file = new File("p.dat"); // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); // oos.writeObject(Num.ONE); // oos.close(); Num.ONE.printValues(); System.out.println("=========反序列化后======="); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Num s1 = (Num) ois.readObject(); s1.printValues(); ois.close(); } public static void main(String... args) throws Exception { testSerializable(); } }反序列化时如果改变
TWO和ONE的位置,那么对应的值也会发生改变ONE ONE.ordinal 1 TWO TWO.ordinal 0 THREE THREE.ordinal 2 =========反序列化后======= //调换(ONE,TWO)的位置: TWO, ONE, THREE; ->ONE, TWO, THREE; ONE ONE.ordinal 0 TWO TWO.ordinal 1 THREE THREE.ordinal 2 -
单例类实现Serializable接口,需要实现readResolve方法,方法单例模式被破坏
public class School implements Serializable{ public String sName = null; public String sId = null; public static final School instance= new School(); // Singleton pattern requires constructors to be private. private School(){ this.sName = ""; this.depId = ""; } // Singleton pattern requires constructors to be private. private School(String name, String id){ this.sName = name; this.spId = id; } private Object readResolve(){ return instance; } }
自定义序列化和反序列化
在序列化类的内部实现readObject,writeObject方法即可实现自定义序列化和反序列化,注意方法签名是有要求的
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
//记得调用默认的读入方法
inputStream.defaultReadObject();
//读入字段,比如
//name = (String)inputStream.readObject();
//score = inputStream.readFloat();
}
private void writeObject(ObjectOutputStream outputStream) throws IOException {
//记得调用默认的写出方法
outputStream.defaultWriteObject();
//写入字段,比如
//outputStream.writeObject(name);
//outputStream.writeFloat(score);
}
序列化代理
前面序列化流程里跳过了代理这部分,这里详细说说
序列化代理通过writeReplace方法实现,使用如下
//待序列化的类
public class Student implements Serializable {
private final static long serialVersionUID = 1L
private String firstName = null;
private String lastName = null;
private Integer age = null;
// unserializable field
private transient School School= null;
public Student () { }
public Student (String fname, String lname, Integer age, School school) {
this.firstName = fname;
this.lastName = lname;
this.age = age;
this.school = school;
}
//调用代理类
private Object writeReplace ( ) {
return new SerializationProxy4Student(this); // this:Student instance
}
}
//Student类的序列化代理类
private class SerializationProxy4Student implements Serializable {
private final static long serialVersionUID = 1L // Any number will do
private String firstName = null;
private String lastName = null;
private Integer age = null;
private String schoolName = null;
private String schoolId = null;
SerializationProxy4Student (Student s) {
this.firstname = s.firstname;
this.lastname = s.lastname;
this. age = s.age;
this.schoolName = s.school.sName;
this.schoolId = s.school.sId;
}
}
使用代理类的好处主要有:
-
不用在主类里实现readObject,writeObject
-
将序列化的内容和类的结构分开。
无论主类(比如Student类)如何修改和被继承,其序列化的格式都是代理类(SerializationProxy4Student类)的序列化格式
-
便于在主类发生变化时支持多种序列化格式