https://github.com/Wasabi1234/Java-Interview-Tutorial
1 什么是序列化、反序列化
Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。
2 为什么需要序列化?
2.1 使用场景
2.1.1 持久化对象
JVM允许我们在内存中创建可复用的Java对象,但一般只有当JVM处于运行时,这些对象才可能存在。即这些对象的生命周期不会比JVM的生命周期更长。
但在现实应用中,可能要求在JVM停止运行之后能够持久化指定对象,并在将来某时重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,后面再将这些字节组装成对象。
2.1.2 网络传输
把Java对象通过网络进行传输时。因为数据只能以二进制形式在网络中传输,所以当把对象通过网络发出去前,需要先序列化成二进制数据,在接收端读到二进制数据后反序列化成Java对象。
对象序列化保存的是对象的”状态”,即其成员变量,所以对象序列化不会关注静态变量。 除了在持久化对象时会用到对象序列化,当使用RMI或在网络中传递对象时,都会用到对象序列化。
Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。
3 如何使用?
以序列化到文件为例,看看Java序列化的基本用法。
package test; import java.io.*; public class SerializableTest { public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); TestObject testObject = new TestObject(); oos.writeObject(testObject); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("temp.out"); ObjectInputStream ois = new ObjectInputStream(fis); TestObject deTest = (TestObject) ois.readObject(); System.out.println(deTest.testValue); System.out.println(deTest.parentValue); System.out.println(deTest.innerObject.innerValue); } } class Parent implements Serializable { private static final long serialVersionUID = -4963266899668807475L; public int parentValue = 100; } class InnerObject implements Serializable { private static final long serialVersionUID = 5704957411985783570L; public int innerValue = 200; } class TestObject extends Parent implements Serializable { private static final long serialVersionUID = -3186721026267206914L; public int testValue = 300; public InnerObject innerObject = new InnerObject(); }
程序执行完用sublime打开temp.out文件,可以看到
aced 0005 7372 0017 636f 6d2e 7373 732e 7465 7374 2e54 6573 744f 626a 6563 74d3 c67e 1c4f 132a fe02 0002 4900 0974 6573 7456 616c 7565 4c00 0b69 6e6e 6572 4f62 6a65 6374 7400 1a4c 636f 6d2f 7373 732f 7465 7374 2f49 6e6e 6572 4f62 6a65 6374 3b78 7200 1363 6f6d 2e73 7373 2e74 6573 742e 5061 7265 6e74 bb1e ef0d 1fc9 50cd 0200 0149 000b 7061 7265 6e74 5661 6c75 6578 7000 0000 6400 0001 2c73 7200 1863 6f6d 2e73 7373 2e74 6573 742e 496e 6e65 724f 626a 6563 744f 2c14 8a40 24fb 1202 0001 4900 0a69 6e6e 6572 5661 6c75 6578 7000 0000 c8
4 序列化原理
调用ObjectOutputStream.writeObject()
和ObjectInputStream.readObject()
后,究竟发生什么?
4.1 ObjectStream Class
类的序列化描述符,该类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可通过lookup()方法来查找、创建在这个JVM中加载的特定的ObjectStreamClass对象。
4.2 序列化 - writeObject()
调用wroteObject进行序列化前会先调用ObjectOutputStream的构造器,生成一个ObjectOutputStream对象:
public ObjectOutputStream(OutputStream out) throws IOException { verifySubclass(); // bout表示底层的字节数据容器 bout = new BlockDataOutputStream(out); handles = new HandleTable(10, (float) 3.00); subs = new ReplaceTable(10, (float) 3.00); enableOverride = false; // 写入文件头 writeStreamHeader(); // flush数据 bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } }
构造器首先把bout绑定到底层的字节数据容器,接着会调用writeStreamHeader:提供了writeStreamHeader方法,于是子类可将自己的头部追加或添加到流中。 将魔数和版本写入流中。
接着调用writeObject执行序列化:
将指定对象写入ObjectOutputStream。 写入对象的类,类的签名以及该类及其所有父类的非transient和非静态字段的值。 可以使用writeObject和readObject方法覆盖类的默认序列化。 该对象引用的对象是可传递的,因此ObjectInputStream可以重建对象的完整等效图。
对于OutputStream的问题和不应序列化的类,将引发异常。 所有异常对于OutputStream都是致命的,OutputStream处于不确定状态,并且取决于调用者忽略还是恢复流状态
public final void writeObject(Object obj) throws IOException { if (enableOverride) { writeObjectOverride(obj); return; } try { // 调用writeObject0()进行序列化 writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } }
正常情况下会调用writeObject0()进行序列化:
基础writeObject / writeUnshared实现
private void writeObject0(Object obj, boolean unshared) { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // 处理先前编写的和不可替换的对象 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; } // 检查替换对象 Object orig = obj; // 要序列化的对象的Class对象 Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays? Class<?> repCl; // 创建描述cl的ObjectStreamClass对象 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(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); } }
生成一个描述被序列化对象类的类元信息的ObjectStreamClass对象
根据传入的需要序列化的对象的实际类型进行不同的序列化操作。从代码里面可以很明显的看到,
对于String类型、数组类型和Enum可以直接进行序列化
如果被序列化对象实现了Serializable对象,则会调用writeOrdinaryObject()方法进行序列化
这里可以解释一个问题:Serializbale接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale接口就能够进行序列化。
答案是:Serializable接口这是一个标识,告诉程序所有实现了”我”的对象都需要进行序列化。
因此,序列化过程接下来会执行到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(); // 写入Object标志位 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(); } } }
在这个方法中首先会往底层字节容器中写入TC_OBJECT,表示这是一个新的Object
/** * new Object. */ final static byte TC_OBJECT = (byte)0x73;
接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()方法实现如下:
private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException { int handle; if (desc == null) { // 如果desc为null writeNull(); } else if (!unshared && (handle = handles.lookup(desc)) != -1) { writeHandle(handle); } else if (desc.isProxy()) { writeProxyDesc(desc, unshared); } else { writeNonProxyDesc(desc, unshared); } }
在这个方法中会先判断传入的desc是否为null,如果为null则调用writeNull()方法
private void writeNull() throws IOException { // TC_NULL = (byte)0x70; // 表示对一个Object引用的描述的结束 bout.writeByte(TC_NULL); }
如果不为null,则一般情况下接下来会调用writeNonProxyDesc()方法,该方法实现如下:
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException { // TC_CLASSDESC = (byte)0x72; // 表示一个新的Class描述符 bout.writeByte(TC_CLASSDESC); handles.assign(unshared ? null : desc); if (protocol == PROTOCOL_VERSION_1) { // do not invoke class descriptor write hook with old protocol desc.writeNonProxy(this); } else { writeClassDescriptor(desc); } Class cl = desc.forClass(); bout.setBlockDataMode(true); if (cl != null && isCustomSubclass()) { ReflectUtil.checkPackageAccess(cl); } annotateClass(cl); bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); writeClassDesc(desc.getSuperDesc(), false); }
在这个方法中首先会写入一个字节的TC_CLASSDESC,这个字节表示接下来的数据是一个新的Class描述符,接着会调用writeNonProxy()方法写入实际的类元信息,writeNonProxy()实现如下:
void writeNonProxy(ObjectOutputStream out) throws IOException { out.writeUTF(name); // 写入类的名字 out.writeLong(getSerialVersionUID()); // 写入类的序列号 byte flags = 0; // 获取类的标识 if (externalizable) { flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; int protocol = out.getProtocolVersion(); if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) { flags |= ObjectStreamConstants.SC_BLOCK_DATA; } } else if (serializable) { flags |= ObjectStreamConstants.SC_SERIALIZABLE; } if (hasWriteObjectData) { flags |= ObjectStreamConstants.SC_WRITE_METHOD; } if (isEnum) { flags |= ObjectStreamConstants.SC_ENUM; } out.writeByte(flags); // 写入类的flag out.writeShort(fields.length); // 写入对象的字段的个数 for (int i = 0; i < fields.length; i++) { ObjectStreamField f = fields[i]; out.writeByte(f.getTypeCode()); out.writeUTF(f.getName()); if (!f.isPrimitive()) { // 如果不是原始类型,即是对象或者Interface // 则会写入表示对象或者类的类型字符串 out.writeTypeString(f.getTypeString()); } } }