明晰|明晰 | Java序列化与反序列化

明晰|明晰 | Java序列化与反序列化
文章图片

小小开始写作,这是本周的第三篇

这是小小本周的第三篇 最近事情有点多,多的让人有点受不了
本篇将会分为实战和理论两部分,依次进行讲解。
实战 含义 序列化:对象写入IO流中,实现对象变成文件。反序列化:把文件中的对象,进行恢复,恢复到内存中,实现反序列化。意义:序列化的最大的意义在于实现对象可以跨主机进行传输,这些对象可以实现保存在磁盘中,并且脱离程序而独立存在。
序列化需要实现一个接口 一个对象需要实现序列化,在这里,需要实现一个接口,这个接口为
java.io.Serializable

点开这个接口,可以看到定义的内容如下
public interface Serializable { }

进行序列化 把一个Java对象变成数组,在这里需要使用一个流,把一个Java对象写入字节流。其代码如下
import java.io.*; import java.util.Arrays; public class Main { public static void main(String[] args) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (ObjectOutputStream output = new ObjectOutputStream(buffer)) { // 写入int: output.writeInt(12345); // 写入String: output.writeUTF("Hello"); // 写入Object: output.writeObject(Double.valueOf(123.456)); } System.out.println(Arrays.toString(buffer.toByteArray())); } }

在这里通过ObjectOutputStream类型,实现把数据写入buffer中。
在这里写入的是基本数据类型,这些基本数据类型为int,boolean,也可以写入String,因为这些基本数据类型都实现了序列化的接口,所以写入写出的内容都很大。
反序列化 ObjectInputStream 负责从一个字节流中读取对象。代码如下
try (ObjectInputStream input = new ObjectInputStream(...)) { int n = input.readInt(); String s = input.readUTF(); Double d = (Double) input.readObject(); }

在这里,为了避免不让Java类序列化时候,出现class类的不兼容。因为一台主机上有class类,另外一台主机上没有该class类,此时,需要进行反序列化。同时标识版本,使用serialVersionUID定义一个静态的版本。例子如下
public class Person implements Serializable { private static final long serialVersionUID = 2709425275741743919L; }

原理 这里将会对Java序列化进行原理性的阐述。原理有点枯燥,客官可以跳过不看
一段序列化代码
public class SerializeTest { public void serialize() throws Exception{ data1 d = new data1(); d.setId(1036); d.setName("data1"); d.setPwd("pwd1"); d.setPwd2("pwd2"); FileOutputStream fos = new FileOutputStream("d:/project/serial/data1"); ObjectOutputStream oos = new ObjectOutputStream(fos); //创建Object输出流对象 oos.writeObject(d); //向data1文件中写入序列化数据data1类 fos.close(); oos.close(); System.out.println("序列化完成"); } public data1 deSerialize() throws Exception{ FileInputStream fis = new FileInputStream("d:/project/serial/data1"); ObjectInputStream ois = new ObjectInputStream(fis); //创建Object输入流对象 data1 d = (data1)ois.readObject(); //从data1文件中反序列化出data1类数据 ois.close(); fis.close(); return d; }public static void main(String[] args) throws Exception{ SerializeTest s = new SerializeTest(); s.serialize(); data1 d = s.deSerialize(); System.out.println("id:"+d.getId()); System.out.println("name:"+d.getName()); System.out.println("pwd:"+d.getPwd()); } }

执行序列化以后,用notdpad++打开以后是这样明晰|明晰 | Java序列化与反序列化
文章图片

用十六进制查看明晰|明晰 | Java序列化与反序列化
文章图片
即,这些是序列化
源码解析 序列化依靠的是ObjectOutputStream。构造参数
public ObjectOutputStream(OutputStream out) throws IOException { verifySubclass(); bout = new BlockDataOutputStream(out); handles = new HandleTable(10, (float) 3.00); subs = new ReplaceTable(10, (float) 3.00); enableOverride = false; writeStreamHeader(); bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } }

其writeStreamHeader代码如下
protected void writeStreamHeader() throws IOException { bout.writeShort(STREAM_MAGIC); bout.writeShort(STREAM_VERSION); }

writeShort是往容器里写两个字节,这里初始化写入了4个字节(一个STREAM_MAGIC ,一个 STREAM_VERSION)
/** * Magic number that is written to the stream header. */ final static short STREAM_MAGIC = (short)0xaced; /** * Version number that is written to the stream header. */ final static short STREAM_VERSION = 5;

即 ac ed 00 05,表示声明使用序列化协议以及说明序列化版本
开始序列化 writeObject()
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 { // handle previously written and non-replaceable objects int h; ...省略代码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 = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; }// 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); } }

后面那些判断,容易看出,根据对象的不同类型,按不同方法写入序列化数据,这里如果对象实现了Serializable接口,就调用writeOrdinaryObject()方法。
然后发现这个方法还传入了一个desc,这是在此函数之前的一个for(; ; )循环里,创建的用来描述该对象类信息的,ObjectStreamClass类。
然后看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(); } } }

先是writeByte(),写入了一个字节的TC_OBJECT标志位(十六进制 73),然后调用writeClassDesc(desc),把之前生成的该类信息写入,跟进看writeClassDesc()
private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException { int handle; if (desc == null) { writeNull(); } else if (!unshared && (handle = handles.lookup(desc)) != -1) { writeHandle(handle); } else if (desc.isProxy()) { writeProxyDesc(desc, unshared); } else { writeNonProxyDesc(desc, unshared); } }

isProxy()判断类是否是动态代理类,没了解过动态代理(先mark),这里因为不是动态代理类,所以会调用
writeNonProxyDesc(desc)跟进writeNonProxyDesc(desc)private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException { 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); }

发现writeByte写入了一个字节的TC_CLASSDESC(16进制 72)
然后下面一个判断是true进入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); 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()) { out.writeTypeString(f.getTypeString()); } } }

调用writeUTF()写入了类名,这个writeUTF()函数,在写入十六进制类名前,会先写入两个字节的类名长度,
然后再调用writeLong,写入序列化UID
然后下面有个判断,会判断类接口的实现方式,调用writeByte()写入一个字节的标志位。
下面是所有标志位
/** * Bit mask for ObjectStreamClass flag. Indicates Externalizable data * written in Block Data mode. * Added for PROTOCOL_VERSION_2. * * @see #PROTOCOL_VERSION_2 * @since 1.2 */ final static byte SC_BLOCK_DATA = https://www.it610.com/article/0x08; /** * Bit mask for ObjectStreamClass flag. Indicates class is Serializable. */ final static byte SC_SERIALIZABLE = 0x02; /** * Bit mask for ObjectStreamClass flag. Indicates class is Externalizable. */ final static byte SC_EXTERNALIZABLE = 0x04; /** * Bit mask for ObjectStreamClass flag. Indicates class is an enum type. * @since 1.5 */ final static byte SC_ENUM = 0x10;

然后调用writeShort写入两个字节的域长度(比如说有3个变量,就写入 00 03 )
接下来是一个循环,准备写入这个类的变量名和它对应的变量类型了
每轮循环:
writeByte写入一个字节的变量类型; writeUTF()写入变量名 判断是不是原始类型,即是不是对象 不是原始类型(基本类型)的话,就调用writeTypeString()
这个writeTypeString(),如果是字符串,就会调用writeString()
而这个writeString()往往是这样写的,字符串长度(不是大小)小于两个字节,就先写入一个字节的TC_STRING(16进制 74),然后调用writeUTF(),写入一个signature,这好像跟jvm有关,最后一般写的是类似下面这串
74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b

"翻译"过来就是,字符串类型,占18个字节长度,变量名是
Ljava/lang/string;

而如果说,之前已经声明过上面这个对象,即前面有这串 74 00 12...3b
那就会调用writeHandle(),先写入一个字节的TC_REFERENCE(16进制 71),然后调用writeInt()写入 007e0000 + handle,这个handle是之前声明过对象的位置,这里我还没弄清除这个位置是怎么定位的,一般是00 01,也就是说 writeHandle(),一般写入如下:
71 00 7e 00 XX 这样5个字节(最后这个00 XX 还不确定,等我再弄明白,一般是 00 01)
上面这些结束了,也就是我们写完了writeNonProxy(),现在再次回到writeNonProxyDesc()
接下来继续调用writeByte()写入一个字节的TC_ENDBLOCKDATA(16进制 78),块结束标志位
再调用writeCLassDesc(),参数是desc的父类,这里如果父类没有实现序列化接口那就不会写入,否则回到刚才writeNonProxyDesc那一步开始写父类的类信息和变量信息(起始位72,终止位78),类似于一个递归调用,最后如果没有实现了序列化接口的父类了,就会调用writeNull(),写入一个字节的TC_NULL(16进制 70),表示没对象了。
好了,总之writeClassDesc()这个递归调用完了之后,我们就回到了writeOrdinaryObject()
接下来调用writeSerialData(),准备写入序列化数据
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); } } }

一个循环,上限是类(包括父类)数量
每轮:
调用defaultWriteFields()
defaultWriteFields() 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++) { if (extendedDebugInfo) { debugInfoStack.push( "field (class "" + desc.getName() + "", name: "" + fields[numPrimFields + i].getName() + "", type: "" + fields[numPrimFields + i].getType() + "")"); } try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } }

先判断是否是基本类型,是的话调用write直接写入序列化数据
否则,获取该类所有变量数,开始循环
这个循环的每轮:
调用writeObject0()写入变量,也就是说,根据变量类型,用相应方法写入。
最后循环结束;
随着所有变量的写入,第一次循环也结束,writeSerialData()方法调用完毕,回到了writeOrdinaryObject(),执行结束回到了writeObject0(),又回到了writeObject()。
明晰|明晰 | Java序列化与反序列化
文章图片

小明菜市场
推荐阅读
● 理论 | 三天两夜,万字长文,吃透TCP/IP
● 应用 | Redis实现 主从,单例,集群,哨兵,配置应用
● 实战 | 后端日志的前世今生
● 实战 | Java 流之Stream,Lambda以及日期
● 理论 | 优雅的构建一个健壮的API接口
【明晰|明晰 | Java序列化与反序列化】明晰|明晰 | Java序列化与反序列化
文章图片

    推荐阅读