Java对象是运行在JVM的堆内存中,如果JVM停止后,它的生命也就戛然而止。为了能在JVM停止后,继续使用Java对象,需要将Java对象持久化(持久化就是将对象进行二进制转化为机器能识别的语言)。序列化使得对象可以脱离程序运行而独立存在。
在以下情况需要使用Java序列化:
序列化版本号ID,是一个用于标识序列化类版本的特殊字段。通常在实现 Serializable 接口的类中使用,用于确保序列化和反序列化的一致性。
点击 Alt + Enter (Intelij IDEA下),可以快速添加serialVersionUID(hash值),如果无法快速新增,可进行如下操作:
setting->Editor->Inspections->搜索UID->勾选Serializable class without ‘serialVersionUID’ 可以检查实现了Serializable的类是否设置了serialVersionUID。
serialVersionUID的生成有以下三种方式:
序列化考虑的优先级:
安全性-》通用性-》兼容性-》性能-》效率-》空间开销
首选的还是 Hessian 与 Protobuf,因为他们在性能、时间开销、空间开销、通用性、 兼容性和安全性上,都满足了我们的要求。
Hessian 在使用上更加方便,在对象的兼 容性上更好。
Protobuf 则更加高效,通用性上更有优势。
JSON序列化和JDK序列化区别?
对于对象转化成json字符串和json字符串转化成对象,也是属于序列化和反序列化的范畴,相对于JDK提供的序列化机制,各有各的优缺点:
下面演示JDK实现序列化与反序列化,示例如下:
准备 Children 类:
package demo.serializable;
import java.io.Serializable;
public class Children implements Serializable {
//显示声明,默认为1L
private static final long serialVersionUID = 1L;
public String unSerializable;
}
准备 Parent 类:
package demo.serializable;
import java.io.Serializable;
public class Parent implements Serializable {
//显示声明serialVersionUID,值为诸多因素计算出的64位的hash值
private static final long serialVersionUID = 2673051781680263602L;
public String name;
public int age;
public transient String address;
public Children children;
}
准备 SerializationUtility 类,进行序列化操作:
package demo.serializable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;
public class SerializationUtility {
public static void main(String[] args) throws IOException {
Parent parent = new Parent();
parent.name = "father";
parent.age = 50;
parent.children = new Children();
String serializedObj = serializeObjectToString(parent);
System.out.println("Serialized Parent object to string:");
System.out.println(serializedObj);
}
/**
* 将对象序列化为 字节序列
*/
public static String serializeObjectToString(Serializable o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
准备 DeserializationUtility 类,进行反序列操作:
package demo.serializable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
public class DeserializationUtility {
public static void main(String[] args) throws ClassNotFoundException, IOException {
String serializedObj = "rO0ABXNyABhkZW1vLnNlcmlhbGl6YWJsZS5QYXJlbnQlGJZu2UbxsgIAA0kAA2FnZUwACGNoaWxkcmVudAAcTGRlbW8vc2VyaWFsaXphYmxlL0NoaWxkcmVuO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAADJzcgAaZGVtby5zZXJpYWxpemFibGUuQ2hpbGRyZW4AAAAAAAAAAQIAAUwADnVuU2VyaWFsaXphYmxlcQB+AAJ4cHB0AAZmYXRoZXI=";
System.out.println("Deserializing Parent...");
Parent deserializedObj = (Parent) deSerializeObjectFromString(serializedObj);
System.out.println("Parent.name:" + deserializedObj.name);
System.out.println("Parent.age:" + deserializedObj.age);
//因为address字段加了 transient 修饰,不会进行序列化和反序列化,
System.out.println("Parent.address:" + deserializedObj.address);
}
/**
* 反序列化读取
*/
public static Object deSerializeObjectFromString(String s) throws IOException, ClassNotFoundException {
byte[] data = Base64.getDecoder().decode(s);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
Object o = ois.readObject();
ois.close();
return o;
}
}
在JDK实现序列化与反序列过程中,不难发现:
原理解析如下:
JDK的序列化过程可以大致理解如下:
写头部:声明序列化协议、版本
写对象:类名、签名、属性名、属性类型、属性值、开头结尾数据。
源码详情如下:
ObjectOutputStream 对象调用 writeObject(“需要序列化的对象”),进行对象的序列化。
/**
* 将对象序列化为 字节序列
*/
public static String serializeObjectToString(Serializable o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//进行序列化
oos.writeObject(o);
oos.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
进入 writeObject 方法,判定是否启用重写,启用则调用重写方法,否则调用 writeObject0(obj, false),代码如下:
public final void writeObject(Object obj) throws IOException {
//判定是否启用重写,启用则调用重写方法,否则调用 writeObject0(obj, false)
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
进入 writeObject0 方法后,代码拉到方法结尾,可以看到多个if-esle 判断,判定是否实现了Serializable接口,若否则抛出异常NotSerializableException,若是则调用 writeOrdinaryObject(obj, desc, unshared) 方法。代码片段如下:
//ObjectOutputStream 在序列化的时候,会判断被序列化的Object是哪一种类型,String?array?enum?还是 Serializable,如果都不是的话,抛出 NotSerializableException异常。
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) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
进入 writeOrdinaryObject 方法,方法中调用了 writeClassDesc(desc, false) 方法,该方法主要用于写入类的信息。写入类信息之后,会写入对象信息。判定是否实现了Externalizable接口,若是则调用 writeExternalData((Externalizable) obj) 自定义的方法,若否则调用 writeSerialData(obj, desc)。代码片段如下:
//写入类信息
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
//判定是否实现了Externalizable接口,是则调用 writeExternalData 的自定义方法,否则调用 writeSerialData 方法,写入对象信息。
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
至此,整个序列化的过程便结束了,如果有兴趣的可以继续深挖 writeExternalData((Externalizable) obj) 方法的内部。