今天来聊聊JDK自带的序列化,企业级软件开发过程中一定会涉及到数据落盘或者网络传输数据,由于Linux操作系统要数据落盘或者传输数据前需要将数据转为二进制数据流,因此我们需要一套规则来将Java世界中的对象(数据)转化为二进制数据流,而JDK提供的序列化就是这套转换规则。如下图,序列化工具将数据从其他格式的数据转为二进制数组的方式并通过stream流的方式进行输出以及输入
因此序列化就是把对象转换成二进制数据,以便于持久化或网络传输;而反序列化是从网络/磁盘读取读取字节序列然后转化为对象或者数据结构。除了JDK自带的序列化,常见的还有protobuf、Kryo、JSON、Hession以及Fury等。这些各有优缺点,但就影响面来说,Java生态中JDK自带的序列化方式影响最广,因此今天有必要跟大家一起聊下JDK的序列化规则,而且只要把JDK的序列化弄明白后再去看其他的也会容易很多
JDK的序列化使用方式应该是最简单的,只需要要序列化的对象继承Serializable接口即可,之后就是用流工具将该对象转换成二进制数组并写到本地磁盘或传输到外部网络,样例如下
public class Main {
public static void main(String[] args) throws Exception {
User user1 = new User("big dog", 5);
try (ObjectOutputStream outputStream =
new ObjectOutputStream(new FileOutputStream("User.txt"))) {
outputStream.writeObject(user1);
outputStream.flush();
}
try (ObjectInputStream inputStream =
new ObjectInputStream(new FileInputStream("User.txt"))) {
User user2 = (User)inputStream.readObject();
System.out.println("user2 is:"+user2.toString());
System.out.println(user1.equals(user1));
System.out.println(user1.equals(user2));
}
}
}
输出如下
user2 is:User{name=‘big dog’, age=5}
true
false
可以看到user2的内容跟user1是一样的,但是通过比较两个对象发现user1并不等于user2,这是因为user2是新生成的对象,两个对象的引用地址不同导致并不相等。再看看输出到User.txt文件的内容
?ísrsequence.jdk.User1?F?eVa"IageLnametLjava/lang/String;xptbig dog
虽然有些乱码,但能够大致看的出来有类全限定名、类的成员属性名、属性类型以及成员属性对应的值。以上就是使用JDK序列化/反序列化的流程,可能工作中我们不会频繁的写序列化代码,但是通信/存储相关的组件里一般都会高频的进行序列化/反序列化操作,因此如果之后我们要自己设计通信/存储的话,掌握序列化是非常有必要的
除此之外使用JDK的序列化时还需要额外注意以下几个知识点
以下是几个值得问题
接下来我们再来看看它的序列化是怎么设计的,
接下来看看它的源码是怎么实现的吧,从这行代码开始往下看吧
objectOutputStream.writeObject(user1);
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
看看writeObject0的实现
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
....
// 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) {
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);
}
}
再继续看下writeOrdinaryObject的实现
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
writeSerialData方法开始进行数据的序列化操作,继续往下跟
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()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
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;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
继续往下跟
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
....
}
} else {
throw new UnsupportedOperationException();
}
}
继续往下看invoke方法
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor;
// read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
走到这里能看到核心方法是desc.getClassDataLayout 和 writeObjectMethod.invoke。感兴趣的可以顺着这两个方法往下跟踪
https://www.cnblogs.com/binarylei/p/10987540.html
https://blog.csdn.net/u011315960/article/details/89963230