Java序列化源码解析
2025-06-10 16:34:27 0 举报
Java序列化源码解析
作者其他创作
大纲/内容
public String readUTF() throws IOException { return bin.readUTF(); }public String readUTF() throws IOException { return readUTFBody(readUnsignedShort()); }
public interface PrivilegedAction<T> { T run();}
结束
int peek() throws IOException { if (blkmode) { if (pos == end) { refill(); } return (end >= 0) ? (buf[pos] & 0xFF) : -1; } else { return in.peek(); } }
和汉字步骤区别
suid = getDeclaredSUID(cl); jvm 自动生成
这里直接使用writeInt 而不是 对象Objout.writeInt(age);
反射获取 serialVersionUID 成员
1、 写入类描述信息font color=\"#569230\
public void writeInt(int val) throws IOException { bout.writeInt(val); }
创建序列化对象person
序列化流程
这里才是真正调用Person 类 实现接口的方法 writeExternal
写入数据 然后 结束
基于实现Externalizable 接口类span style=\
前面做好铺垫,各种校验是否是属于Serializable接口,当 类描述符对象 desc 记录是Externalizable 对象,自然直接将 序列化对象强制转换 Externalizable 对象调用 writeExternal 方法***源码中写死了,调用writeExternal 方法 所以重写此方法可以自定义序列化
public class Animal implements Serializable { private String color;}public class BlackCat extends Animal implements Serializable { private static final long serialVersionUID = 1L; private String name; private static int age = 199;}
obj.writeExternal(this);
FileOutputStream 作为构造参数创建 ObjectOutputStream
String 描述 serialPersistentFields 类字符串在序列化流协议中是特殊情况。
所以为什么需要一定是 static 和 final 修饰 序列化 ID 才有效果
font color=\"#5c4038\
***序列化对象底层其实 将对象的属性值循环 调用 oos.writeObject(属性值) 一一写入到 ObjectOutputStream 对象 oos 中。默认序列化: 那么肯定直接使用反射机制获取属性值 在循环调用 .writeObject()方法Externalizable 自定义序列化: 重写 writeExternal() 执行 writeObject()。序列化可以做特殊操作,顺序、内容动态。
一个int 数 4个字节 * 8位bit = 32位bit 来表示一个最大整数
Java 序列化 源码解析
off 偏移量 已经被算出来存放的位置下标 10 存放数据长度。 暂时作用不清楚16位val 向右移动八位 只保留前八位
ObjectStreamClass
创建 FileOutputStream 指定输出路径
默认序列化总结: 1、创建类描述符对象 以及 父类 类描述符对象2、jvm自动生成序列化 ID3、利用序列化对象反射的字段创建 字段描述符4、获取最上层Object父类的构造器对象 或者 没有实现 Serializable 接口 父类对象5、写入类描述符信息 5.1、开始标志信息 5.2、写入类描述符信息 5.3、写入 包.类名 和 序列化 ID 5.4、写入可序列化参数flag 5.5、写入字段描述符信息,字段长度,字段TypeCode ,字段 name,字段TypeString 5.6、写入结束标志 5.7、子类 的 类描述符信息写完了,递归再写父类的 类描述符 从 5.1 再开始执行6、写入实际信息 6.1、获取所有类描述符信息,包括父类 6.2、先写入父类 字段信息 再 子类 6.2.1、获取字段描述符对象 6.2.2、根据字段描述符Obj字段数量创建数组 objVals[] 6.2.3、获取字段值 赋值给 objVals[] -----------> 类描述符 内部类中 字段反射器对象获取 字段值 FieldReflector.getObjFieldValues() native 7、执行将真正写入数据 span style=\
判断是否为空,之前汉字已经存入进去获益直接获取 String 得类描述符对象 ObjectStreamClass if (ref != null) { entry = ref.get(); } if (entry instanceof ObjectStreamClass) { // check common case first return (ObjectStreamClass) entry; }
如果属性中有不实现序列化接口的对象会抛出异常吗?
这里也是 类描述符 ObjectStreamClass 类方法所以这里直接固定写死 序列化ID 的 变量名字
static ObjectStreamClass font color=\"#4669ea\
@CallerSensitive public static native <T> T doPrivileged(PrivilegedAction<T> action);
1、校验是否实现 Serializable 类型if (serializable) { // 使用 AccessController.doPrivileged() 用来做特权操作,以便再安全管理器做一些受限制的操作,比如有了特权可以访问私有的成员 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ....... 2、根据 class 对象 获取序列化 ID suid = getDeclaredSUID(cl); try { 3、获取序列化成员变量 fields = getSerialFields(cl); computeFieldOffsets(); 使用默认序列化会计算偏移量 } ...... 4、获取构造器 if (externalizable) { 4.1 反射返回一个 公共无参构造函数 Constructor 对象 如没将返回null后续抛出异常 ,这也是为什么 Externalizable 要实现无参构造器 cons = getExternalizableConstructor(cl); } else {font color=\"#358290\
基于 Serializable 反序列化
byte peekByte() throws IOException { int val = peek(); if (val < 0) { throw new EOFException(); } return (byte) val; }
默认序列化 案例类
获取无参构造器
ObjectInputStream
1.1.2.1 使用反射获取基本信息 和底层使用 native 判断是否实现接口,代理和枚举类型。 最终赋值给 ObjectStreamClass 对象
doPrivileged 会调用 匿名内部类的 run 方法
4、普通String 如何写入
可以猜测?
import java.io.*;public class Main { public static void main(String[] args) { // 创建一个 Person 对象 Person person = new Person(\"张三\
主要内容
1、 写入类描述符 和父类描述符以及标志信息
基于实现父类子类Serializable 接口序列span style=\
从给定的输入流中读取非代理类描述符信息。生成的类描述符功能不完全正常;它只能用作 ObjectInputStream.resolveClass() 和 ObjectStreamClass.initNonProxy() 方法的输入
默认序列化字段
5、int 如何写入
写入 类描述符 对象 参数
如果对象中还存在其它实体类,那么将会递归 到 写入基本参数
1、查找 或创建 class 对象
默认Serializable 序列化
返回 null 的 ObjectStreamField[] 数组例子 实现接口 是 Externalizable 所以不走默认方法
1.1、查找并返回给定类的类描述符1.2、如果类不可序列化且“all”参数为 false,则返回 null。
3、使用 FileOutputStream.writeByte 方法 native 写入 文件中上述代码执行完都将 实现最终写入操作} finally { depth--; bout.setBlockDataMode(oldMode); }
使用 ObjectOutputStream 将 person 对象输出到文件oos.writeObject(black);
返回描述给定类的可序列化字段的 ObjectStreamField 数组
3.1、String对象 汉字 写入流中
为什么要查找? 那肯定是使用缓存技术了。
2、当校验条件一切ok 并且得知序列化对象是 Externalizable 接口将在此处调用 自定义序列化方法 writeExternal
这里得 out 是 创建 ObjectOutputStream 得构造参数 FileOutputStream 对象执行完将把数据写入到文件中
结束
这是一个静态方法 传入 class 对象,并且默认传入 true 可以序列化
private ObjectStreamClass readClassDesc(boolean unshared) throws IOException { byte tc = bin.peekByte(); 返回 114 return switch (tc) { case TC_NULL -> (ObjectStreamClass) readNull(); case TC_PROXYCLASSDESC -> readProxyDesc(unshared); case TC_CLASSDESC -> readNonProxyDesc(unshared); 类描述符 case TC_REFERENCE -> { var d = (ObjectStreamClass) readHandle(unshared); // Should only reference initialized class descriptors d.checkInitialized(); yield d; } default -> throw new StreamCorruptedException( String.format(\"invalid type code: %02X\
判断是否只实现 Serializable 接口
读取非动态代理类描述符对象,将 passHandle 设置为句柄,如果无法返回 VM 中的类,将抛出异常
获取构造器,如果父类有实现 Serializable 接口 就会一直网上找,直到 Object 。这里父类子类都实现 Serializable 接口 所以都是 Object 构造器函数
十六进制数
方法和 步骤一相似
由于Person父类是Object 是没有实现接口,并且父类默认为false 所以返回空后续测试
如果只实现 Seriaizable 那么 调用 默认获取序列化字段方法
2、写入实际信息
font color=\"#314aa4\
int peek() throws IOException { if (peekb >= 0) { 为 -1 之前获取修改过 return peekb; } peekb = in.read(); 为 114 totalBytesRead += peekb >= 0 ? 1 : 0; return peekb; 返回 114 }
递归走 步骤1
自定义序列化操作
···根据上述以及上下文解析得知: 这里如果类字段定义存在 serialPersistentFields 字段,将不需要调用 默认获取字段集合对象。 因为 String 是特殊的,它里面也没有需要序列化字段,它本身就是需要写入的数据。所以它字段返回 空的 ObjectStreamField[]普通实体类 是需要调用 getDefaultSerialFields(cl); fields = getDefaultSerialFields(cl); 因为获取到才能反射所有字段内容写入 输出流对象中。
1.1.2.2 反射获取父类 class 对象,并且使用递归调用 lookup 方法,但是父类默认是 没有序列化 false
父类实现序列化
private native int read0() throws IOException; 为什么返回 114?
先写子类描述信息,再写父类描述信息
1.1.2.3 判定是否实现 Serializable 接口 如果实现获取 序列化 ID 和无参构造器
一个 int 占用 4个字节,这里使用数组 4个位置保存 4个字节内容。每一个字节占用 8位bit 所以,需要右移八位得倍数。得出一个字节得数据
implements Externalizable
ObjectStreamFIeld 保存 序列化字段信息主要还是 反射出来得 Field 对象
oos.flush(); oos.close();
private Object readOrdinaryObject(boolean unshared) throws IOException { 这里 还是获取115 是不是构建时候已经赋值参数了 if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException(\"invalid class descriptor\
调用 writeExternal 方法发现在这里还是使用 ObjectOutputStream.writeObject()和之前序列化对象一样oos.writeObject(o);
static final byte TC_CLASSDESC = (byte)0x72; 114
1、转换二进制等于 8 和 16 所以 | 或 运算 等于 242、这里使用 & 运算来判定是否 包含 static 和 final 关键字3、返回这个静态 序列化字段 值
@Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println(\"person writeExternal...\"); out.writeObject(address); out.writeObject(name); out.writeInt(age); }
为什么要创建 ObjectStreamClass 对象?在创建时会检测 数据类型接口、序列化ID 修饰符、无参构造器。后续会直接调用 writeExternal 方法。所以创建 ObjectStreamClass 对象是为了做校验,验证是否具有序列化资格。
把数据长度,和数据内容 都存储在 buf 字节数组中private final byte[] buf = new byte[MAX_BLOCK_SIZE];
3、汉字究竟如何写入字节流
ObjectInputStream.classprivate ObjectStreamClass readNonProxyDesc(boolean unshared) throws IOException { 校验是否是 类描述符 if (bin.readByte() != TC_CLASSDESC) { throw new InternalError(); } ObjectStreamClass desc = new ObjectStreamClass(); int descHandle = handles.assign(unshared ? unsharedMarker : desc); passHandle = NULL_HANDLE; ObjectStreamClass readDesc; try { readDesc = readClassDescriptor(); } catch (ClassNotFoundException ex) { throw (IOException) new InvalidClassException( \"failed to read class descriptor\").initCause(ex); } Class<?> cl = null; ClassNotFoundException resolveEx = null; bin.setBlockDataMode(true); final boolean checksRequired = isCustomSubclass(); try { if ((cl = resolveClass(readDesc)) == null) { resolveEx = new ClassNotFoundException(\"null class\
循环获取父类 直到父类没有实现 Serializable 接口 或者 没有父类 结果:要么 Object , 要么自己写的实体类 并没有实现 Serializable 接口 就需要提供无参构造器所以序列化 子类时 父类没有实现 Serializable接口时 需要提供无参构造器
public int readUnsignedShort() throws IOException { if (!blkmode) { pos = 0;font color=\"#e74f4c\
开始
1、这里获取 序列化对象得 字节码对象。说明后续肯定回使用到反射技术。2、并且返回一个 类描述符对象,后续使用
写入字段名称
在 Java 中,一个汉字通常占用两个字节,因为 Java 使用的是 Unicode 字符编码,其中汉字字符使用了 16 位编码。但是也有一些特殊情况,例如采用 UTF-8 编码时,一个汉字可能占用三个字节,具体占用多少字节取决于所使用的字符编码。这里String 采用 UTF-8 三个字节
1、lookup 方法 1.1 new ObjectStreamClass() 1.1.1 校验是否是 Serializable 接口 true 1.1.2 获取序列化 ID String 源码中定义有 private static final long serialVersionUID = -6849794470754667710L; 1.1.3 获取序列化字段对象 由于String 实现 Serializable 接口 所以调用 getDeclaredSerialFields() 判断 最终返回空的ObjectStreamField[] 对象 1.1.4 获取父类无参构造器对象2、String 对象写入 buf 字节数组中 if (obj instanceof String) {font color=\"#569230\
public static final int STATIC = 0x00000008; public static final int FINAL = 0x00000010;
0 条评论
下一页