深入学习Java序列化,Java笔试题编程题

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();

}

程序执行完用vim打开temp.out文件,可以看到

0000000: aced 0005 7372 0020 636f 6d2e 6265 6175 …sr. com.beau

0000010: 7479 626f 7373 2e73 6c6f 6765 6e2e 5465 tyboss.slogen.Te

0000020: 7374 4f62 6a65 6374 d3c6 7e1c 4f13 2afe stObject…~.O.*.

0000030: 0200 0249 0009 7465 7374 5661 6c75 654c …I…testValueL

0000040: 000b 696e 6e65 724f 626a 6563 7474 0023 …innerObjectt.#

0000050: 4c63 6f6d 2f62 6561 7574 7962 6f73 732f Lcom/beautyboss/

0000060: 736c 6f67 656e 2f49 6e6e 6572 4f62 6a65 slogen/InnerObje

0000070: 6374 3b78 7200 1c63 6f6d 2e62 6561 7574 ct;xr…com.beaut

0000080: 7962 6f73 732e 736c 6f67 656e 2e50 6172 yboss.slogen.Par

0000090: 656e 74bb 1eef 0d1f c950 cd02 0001 4900 ent…P…I.

00000a0: 0b70 6172 656e 7456 616c 7565 7870 0000 .parentValuexp…

00000b0: 0064 0000 012c 7372 0021 636f 6d2e 6265 .d…,sr.!com.be

00000c0: 6175 7479 626f 7373 2e73 6c6f 6765 6e2e autyboss.slogen.

00000d0: 496e 6e65 724f 626a 6563 744f 2c14 8a40 InnerObjectO,…@

00000e0: 24fb 1202 0001 4900 0a69 6e6e 6572 5661 $…I…innerVa

00000f0: 6c75 6578 7000 0000 c8 luexp…

第三部分:Why

========

调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()之后究竟做了什么?temp.out文件中的二进制分别代表什么意思?

别急,且听我娓娓道来。

Ⅰ.ObjectStreamClass类


官方文档对这个类的介绍如下

Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method.

可以看到ObjectStreamClass这个是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象。

Ⅱ.序列化: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(); // 写入文件头

bout.setBlockDataMode(true); // flush数据

if (extendedDebugInfo) {

debugInfoStack = new DebugTraceInfoStack();

} else {

debugInfoStack = null;

}

}

构造函数中首先会把bout对绑定到底层的字节数据容器,接着会调用writeStreamHeader()方法,该方法实现如下:

protected void writeStreamHeader() throws IOException {

bout.writeShort(STREAM_MAGIC);

bout.writeShort(STREAM_VERSION);

}

在writeStreamHeader()方法中首先会往底层字节容器中写入表示序列化的Magic Number以及版本号,定义为

/**

  • 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;

接下来会调用writeObject()方法进行序列化,实现如下:

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()进行序列化操作,该方法实现如下:

private void writeObject0(Object obj, boolean unshared)

throws IOException

{

// 一些省略代码

try {

// 一些省略代码

Object orig = obj;

// 获取要序列化的对象的Class对象

Class cl = obj.getClass();

ObjectStreamClass desc;

for (;😉 {

Class repCl;

// 创建描述cl的ObjectStreamClass对象

desc = ObjectStreamClass.lookup(cl, true);

// 其他省略代码

}

// 一些省略代码

// 根据实际的类型进行不同的写入操作

// 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);

}

}

从代码里面可以看到,程序会

  1. 生成一个描述被序列化对象的类的类元信息的ObjectStreamClass对象。

  2. 根据传入的需要序列化的对象的实际类型进行不同的序列化操作。从代码里面可以很明显的看到,对于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();

bout.writeByte(TC_OBJECT); // 写入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());

}

}

}

writeNonProxy()方法中会按照以下几个过程来写入数据:

调用writeUTF()方法写入对象所属类的名字,对于本例中name = com.beautyboss.slogen.TestObject.对于writeUTF()这个方法,在写入实际的数据之前会先写入name的字节数,代码如下:

  1. 调用writeUTF()方法写入对象所属类的名字

对于本例中name = com.beautyboss.slogen.TestObject.对于writeUTF()这个方法,在写入实际的数据之前会先写入name的字节数,代码如下:

void writeUTF(String s, long utflen) throws IOException {

if (utflen > 0xFFFFL) {

throw new UTFDataFormatException();

}

// 写入两个字节的s的长度

writeShort((int) utflen);

if (utflen == (long) s.length()) {

writeBytes(s);

} else {

writeUTFBody(s);

}

}

  1. 接下来会调用writeLong()方法写入类的序列号UID,UID是通过getSerialVersionUID()方法来获取。

  2. 接着会判断被序列化的对象所属类的flag,并写入底层字节容器中(占用两个字节)。类的flag分为以下几类:

  • final static byte SC_EXTERNALIZABLE = 0x04;表示该类为Externalizable类,即实现了Externalizable接口。

  • final static byte SC_SERIALIZABLE = 0x02;表示该类实现了Serializable接口。

  • final static byte SC_WRITE_METHOD = 0x01;表示该类实现了Serializable接口且自定义了writeObject()方法。

  • final static byte SC_ENUM = 0x10;表示该类是个Enum类型。

对于本例中flag = 0x02表示只是Serializable类型。

  1. 第四步会依次写入被序列化对象的字段的元数据。
  • 首先会写入被序列化对象的字段的个数,占用两个字节。本例中为2,因为TestObject类中只有两个字段,一个是int类型的testValue,一个是InnerObject类型的innerValue。

  • 依次写入每个字段的元数据。每个单独的字段由ObjectStreamField类来表示。

  • 写入字段的类型码,占一个字节。 类型码的映射关系如下:

TypeCode| Java Type

—|—

B | byte

C | char

D | double

F | float

I | int

J | long

L | class or interface

S | short

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

Java架构进阶面试及知识点文档笔记

这份文档共498页,其中包括Java集合,并发编程,JVM,Dubbo,Redis,Spring全家桶,MySQL,Kafka等面试解析及知识点整理

image

Java分布式高级面试问题解析文档

其中都是包括分布式的面试问题解析,内容有分布式消息队列,Redis缓存,分库分表,微服务架构,分布式高可用,读写分离等等!

image

互联网Java程序员面试必备问题解析及文档学习笔记

image

Java架构进阶视频解析合集

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

12744604354)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-zrcNIGaW-1712744604354)]

最后

Java架构进阶面试及知识点文档笔记

这份文档共498页,其中包括Java集合,并发编程,JVM,Dubbo,Redis,Spring全家桶,MySQL,Kafka等面试解析及知识点整理

[外链图片转存中…(img-7vBStfxA-1712744604354)]

Java分布式高级面试问题解析文档

其中都是包括分布式的面试问题解析,内容有分布式消息队列,Redis缓存,分库分表,微服务架构,分布式高可用,读写分离等等!

[外链图片转存中…(img-Y48bucMp-1712744604355)]

互联网Java程序员面试必备问题解析及文档学习笔记

[外链图片转存中…(img-zkpI9xG5-1712744604355)]

Java架构进阶视频解析合集

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-3Tg1fUUm-1712744604355)]

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值