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

这是小小本周的第三篇 最近事情有点多,多的让人有点受不了

本篇将会分为实战和理论两部分,依次进行讲解。

实战

含义

序列化:对象写入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++打开以后是这样

用十六进制查看即,这些是序列化

源码解析

序列化依靠的是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 objectsint h;...省略代码if (obj instanceof ObjectStreamClass) {writeClassDesc((ObjectStreamClass) obj, unshared);return;}// check for replacement objectObject 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 casesif (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 protocoldesc.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 = 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()。

小明菜市场

推荐阅读

● 理论 | 三天两夜,万字长文,吃透TCP/IP

● 应用 | Redis实现 主从,单例,集群,哨兵,配置应用

● 实战 | 后端日志的前世今生

● 实战 | Java 流之Stream,Lambda以及日期

● 理论 | 优雅的构建一个健壮的API接口

明晰 | Java序列化与反序列化相关推荐

  1. java 序列化概念和作用_结合代码详细解读Java序列化与反序列化概念理解

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  2. java序列化与反序列化(转)

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  3. java序列化和反序列化以及序列化ID的作用分析

     java序列化和反序列化 一.概念 java对象序列化的意思就是将对象的状态转化成字节流,以后可以通过这些值再生成相同状态的对象.对象序列化是对象持久化的一种实现方法,它是将对象的属性和方法转化为一 ...

  4. Serializable详解(1):代码验证Java序列化与反序列化

    说明:本文为Serializable详解(1),最后两段内容在翻译上出现歧义(暂时未翻译),将在后续的Serializable(2)文中补充. 介绍:本文根据JDK英文文档翻译而成,本译文并非完全按照 ...

  5. java序列化与反序列化全讲解

    目录 1 概述 序列化与反序列化 为什么需要序列化与反序列化 几种常见的序列化和反序列化协议 2 序列化实现 Serializable 接口的基本使用 Serializable 接口的特点 3 序列化 ...

  6. Java中如何引用另一个类里的集合_【18期】Java序列化与反序列化三连问:是什么?为什么要?如何做?...

    Java序列化与反序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程: 序列化:对象序列化的最主要的用处就是在传递和保存对象 ...

  7. java序列化和反序列化_Java恶意序列化背后的历史和动机

    java序列化和反序列化 与Java的序列化机制相关的问题已广为人知. 有效的Java 1st Edition (第10章)和有效的Java 2nd Edition (第11章)的整个最后一章都专门讨 ...

  8. 教你彻底学会Java序列化和反序列化

    Java序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程.反序列化:客户端重文件,或者网络中获取到文件以后,在内存中重构对象 ...

  9. 理论 | 教你彻底学会Java序列化和反序列化

    这是小小本周的第四篇 Java序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程.反序列化:客户端重文件,或者网络中获取到文件 ...

最新文章

  1. 修身论文2000字_那些没能写出毕业论文的博士生,究竟是败在了哪里?
  2. php 获取搜狗微信 sn,PHP 获取百度和搜狗收录量 代码 可用于EMLOG
  3. Base64编码原理与应用
  4. 3.10 十进制转换为二进制
  5. 前端学习(1388):多人管理项目8user登录
  6. 项目实战-药品采购系统-day01
  7. iPhone开发笔记[1/50]:初学iPhone上用Quartz 2D画图
  8. [转]Effective C#原则4:用条件属性而不是#if
  9. 海盐商贸学校计算机类试卷,海盐县商贸学校
  10. 怎么实现单击span时给span添加边框
  11. sprint3个人总结
  12. HTML5+CSS3+Bootstrap开发静态页面嵌入android webview中
  13. Tekla二次开发入门经典例子
  14. 《畅玩NAS》第8章 ZeroTier组建局域网
  15. 本安计算机电缆执行标准,阻燃本安计算机信号电缆ZR-IA-DJYPVRP-1*2*1.5
  16. biosrecovery什么意思_recovery是什么意思
  17. 挣值如何计算?(转载)
  18. 移植0.96四针脚OLED程序到msos系统
  19. 老祖宗的绝招对治腰疼,别说你没听过
  20. c语言编程输出1000以内能被3整除的数,【C语言】找出1000以内可以被3整除的数

热门文章

  1. JAVA并发编程实践笔记
  2. eclipse生成boolean型变量的getter是is开头
  3. Dnscrypt_wrapper 服务端的安装与配置
  4. IOS组件绑定无效错误
  5. mysql GRANT
  6. 禁止和开启组策略的批处理
  7. Oracle Dba手记(读书笔记)
  8. JDK源码(8)-Byte
  9. 山东理工oj答案java_众数问题(山东理工OJ)
  10. MySQL中关于OR条件的优化