理论上 这是基础中的基础,但是说实话越是基础越是不容易去细看,我一直只有模糊的概念,比如以Java为例,一个对象返回给前端是用的 json,对象->json 的过程就是序列化,json 到对象都过程就是反序列化,但是具体是对错与否,帮我进行这个操作的函数、框架下面是不是还有更多内容,却从未深究,这次水一篇博客来讲一讲这是什么意思

定义

互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。在OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象–这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。

  • 序列化: 将数据结构或对象转换成二进制串的过程
  • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

这里摘自 美团技术团队 15 年的文章,我也就从这里开始引入吧

通俗比喻

假设你家有很多家具:沙发、电视、冰箱、床……

搬家的时候,你得把它们拆开、装进箱子,贴上标签 —— 这就是序列化

到了新家,打开箱子、按照标签重新组装、恢复成原来的样子 —— 这就是反序列化

01-moving-metaphor.png

由浅入深

现在从最常见的序列化协议入手,先是 json ,这个在现代系统里面最常见了,打开web 也好,网络请求很多都是用 json 传输内容的。那么

who is ‘Json’

json
1
2
3
4
{
  "name": "Ray",
  "age": 25
}

大概就长这样,如果我把它放到 Java 对象里面 它可以是这样的

java
1
2
3
User user = new User();
user.setName("Ray");
user.setAge(25);

这个 user 实际上存在于内存中。

而 Java 内存里是怎么存的?

栈(Stack)里只保存引用:user → 0x1008

真正的数据在堆(Heap)里面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
0x1008 (User 对象)
┌──────────────────────────────┐
│ Object Header                │
│ klass pointer                │
│                              │
│ name ───→ 0x2010             │
│ age  = 25                    │
└──────────────────────────────┘

0x2010 (String 对象)
┌─────────────┐
│ value="Ray" │
└─────────────┘

02-stack-heap-references.png

可以看到:

  • user 变量,只保存了一个引用(地址)
  • User 对象里面有自己的字段
  • name 字段又指向另一个 String 对象

JVM 里面这个对象什么样子

HotSpot JVM 中,一个对象大概是这样布局。

显示已折叠代码(21 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
User Object

┌──────────────────────┐
│      Object Header   │  ←────── 对象头
├──────────────────────┤
│ Mark Word            │
├──────────────────────┤
│ Klass Pointer        │
└──────────────────────┘

┌──────────────────────┐
│      Instance Data   │  ←────── 你的成员变量
├──────────────────────┤
│ name (Reference)     │
├──────────────────────┤
│ age (int)            │
└──────────────────────┘

┌──────────────────────┐
│ Padding              │  ←────── 内存对齐
└──────────────────────┘

03-jvm-object-layout.png

第一部分:对象头(Object Header)

这里放的是 JVM 自己要用的信息,例如:

  • 哈希值
  • GC 标记
  • 锁状态
  • 指向 Class 的指针

这些和你的业务一点关系都没有。


第二部分:字段

name 这里存的并不是 "Ray" 本身,而是 0x2010,也就是 String 对象的地址(引用)。

age 由于 int 是基本类型,值 25 直接存进去。

所以对象真正更像:

1
2
3
4
5
6
User

┌────────────┐
│name=0x2010 │
│age=25      │
└────────────┘

String 又是一个对象,指向一个地址:

1
2
3
4
String
┌────────────────┐
│ value=0x3000   │
└────────────────┘

Java 9 以后内部实际上是 byte[],早期版本是 char[]。而这个 byte[] 也是一个对象,最终存储的是:

1
byte[] → 82 97 121 → R a y

R=82, a=97, y=121,对应 ASCII 码)


那为什么不能直接发送 User?

假设现在 user → 0x1008,网络发送的时候,发送这个地址一点意义都没有。

因为 0x1008 只是这一台机器 JVM 的内存地址。对象本身无法跨进程,更无法跨机器。

04-cross-machine-address.png


所以需要”翻译”

必须把对象翻译成一种任何语言都认识、任何机器都认识的格式

例如 User 对象 → {“name”:”Ray”,”age”:25}

注意这里已经没有 0x1008、没有 Object Header、没有 Mark Word、没有 GC信息,全部不要。只留下真正的数据:name=Rayage=25

所以 JSON 本质就是:把对象里面真正有意义的数据提取出来,用一种文本格式描述出来。


那序列化到底是什么?

回到最初的定义:

  • 序列化:把内存中的对象,转换成一段可以存储或传输的字节序列
  • 反序列化:把字节序列还原回内存中的对象

JSON 只是序列化协议的一种(文本格式,人类可读)。除此之外还有很多选择:

协议格式特点
JSON文本可读性好,通用性强,体积较大
Protobuf二进制体积小,速度快,需要预定义 Schema
Java 原生序列化二进制仅限 Java 生态,安全隐患多
MessagePack二进制类似 JSON 但更紧凑
XLM文本比 JSON 更冗长,支持属性、命名空间等;表达能力强

所以序列化不只是”对象变 JSON”,而是一个更广泛的概念:让数据脱离内存、脱离语言、脱离机器,变得可传输、可存储。


参考文章

序列化和反序列化 | 美团 · 技术团队