0基础带你精通Java对象序列化--以Hessian为例|得物技术
一、概述
在高级编程语言的世界中,开发者始终与**【object/struct】**这类高度抽象的数据结构打交道。然而在分布式架构下,任何服务进程都不是数据孤岛——跨进程数据交换是必然需求。
以Java为例,业务逻辑的输入输出都是**【object】。但在RPC场景中,这些对象必须经由网络传输。这里出现了一个根本性矛盾:网络介质(网线/光纤)对面向对象编程(OOP)一无所知,它们只会用光和电忠实地传输扁平化**的字节流(byte[])。
软件工程经典的分层理论驱使我们去添加一个转换层。
我们需要有个工具或者组件来协助进行**【object】和【byte[]】之间的双向转换**。这个过程包含两个对称的流程:
- 【object】->【byte[]】:业界一般称为序列化/serialize,但是那个单词念起来很拗口,本文我们都叫它【编码/encode】好了。
- 【byte[]】->【object】:业界一般称为反序列化/deserialize,但是那个单词念起来很拗口,本文我们都叫它【解码/decode】好了。
Hessian作为Java生态中久经考验的对象编解码器,相较于同类产品具有以下两大核心优势:
- 深度Java生态适配:与JSON、Protobuf等语言中立的通用协议不同,Hessian专为Java深度优化,对泛型、多态等Java特有语言特性提供原生支持。
- 高效二进制协议:相较JSON等文本协议,Hessian采用精心设计的二进制编码方案,在编解码效率和数据压缩率方面表现更优。
需要强调的是,软件工程没有银弹——业务场景的差异决定了编解码器的选择必然需要权衡取舍。但就Java RPC而言,Hessian应该是经过广泛实践验证的稳健选择。
本文将系统解析Hessian的编码流程,重点揭示其实现【object】->【byte[]】转换的核心机制。
二、基础编码原理
对象编码过程主要包含如下两大核心:
-
对象图遍历:遍历高级数据结构
- 通过反射或元编程技术遍历对象图(Object Graph)。
- 是同类产品的通用逻辑,不管jackson、fastjson、hessian都需要用不同的方式做类似的事情。
-
编码格式:将高级数据结构按协议拍平放到byte[]
- 同类产品百家争鸣,各有各的思路。
- 是同类产品的竞技场,各个产品在这里体现差异化的竞争力。
- 设计权衡包括:
- 二进制效率 vs 可读性(如Hessian二进制 vs JSON文本)
- 编码紧凑性 vs 扩展灵活性
- 跨语言支持 vs 语言特性深度优化
对象图遍历决定了编码能力的下限(能否正确处理对象结构),而编码格式决定了编码能力的上限(传输效率、兼容性等)。
对象图遍历
对象图遍历的本质是按深度优先进行对象属性导航。
举个例子:
宏观来看,A类型的对象其实是一棵树(或图),如果脑补不出来的话,我给你画个图:
可以看到这棵树的叶子结点都一定是Java内置的基本数据类型。换句话说,Java的8种基础数据类型和他们的数组变体,支撑了Java丰富的预定义/自定义数据结构。
八股文:Java的8种基础数据类型是哪些?String算不算基础数据类型?
编码的本质就是深度优先的遍历这棵树,拍平它,然后放到**byte[]**里。
我举个例子吧。
伪代码
为降低伪代码复杂度,我们假设Java只有1种基础数据类型int,也就是说Java里只有int和只包含int字段的自定义POJO。
我们定义POJO指的是用于传输、存储使用的简单Java Bean或者常说的DTO。
从某种意义上来说,Integer也是基于int封装的自定义POJO。
字节流抽象
我们使用标准库里的java.io.DataOutput来进行伪代码说理,这个类提供了一些语义化的编码function。
java.io.DataOutput
对象图遍历
字节流布局
最终呈现出来的字节流层面的数据布局会是这样:
看起来没毛病,唯一的问题就是不好解码。
当解码端收到一个16字节的字节流以后,它分不清哪块数据是A对象的,哪块数据是B对象的。甚至都分不清这到底是4个int32还是2个int64。
这个问题需要编码格式来解决。
编码格式
上面遗留的问题,聪明的你肯定想到了答案。
就是因为编码产物太太太简陋了,整个过程中只是一股脑的把树拍平,把叶子节点的值写入字节流,缺少结构元数据。
最最最重要的结构元数据就是数据块的边界,上述4个数据块,最起码应该添加3个边界标识。
我们先用我们耳熟能详的JSON格式来理解下编码格式这个事情。
伪代码
JSON是这样解决这个问题的:
JSON协议在嵌套的POJO上用**{}来作为边界,POJO内部的字段键值用 , 来做边界,:**拆分字段键值。
字节流布局
结果就变成这样:
这样在解码的时候,可以通过**{、}、,、:** 等token来切割JSON字符串,判定数据块边界并恢复出对象图。
三、Hessian编码格式
接下来我们可以开始介绍Hessian的编码魔法了。
需要强调的是:Hessian跟JSON不同,Hessian是二进制格式。如果一个字节流直接按字符集解码不能得到一个完整的、有意义的字符串,那它就是二进制编码数据。
Hessian在编码时,按数据块类型为每一个数据块添加一个前缀字节(byte)作为结构元数据,这些元数据和数据块一起,交给解码端使用。
数据块
对象图里的每一个节点,都是一个数据块。
如上图所示,以A对象为根的对象图,一共有6个数据块。
数据块标签(tag)
Hessain在编码每一个数据块时,都会根据数据块的类型在字节流中写入一个前缀字节(0-255),这个字节说明了数据块的语义和结构。
以int32为例,其最基础的编码格式如下:
除该基础编码格式外,int32的编码还有其他变体。
上述 I 就是整数类型的tag。解码端读取tag后,按tag值来解码数据。
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject(java.util.List<java.lang.Class<?>>)
由此延伸、拓展,其他的数据类型都是类似的模式。常见数据类型及其对应的tag值如下:
值得注意的是,N、F、T三个tag是自解释的,和固定值映射、绑定。
POJO编码
POJO是一种特殊的数据块,Hessian将POJO的结构和值拆开,分别编码。
POJO结构编码
POJO结构的tag为C,对照int32的编码格式,POJO结构的编码格式如下:
举个例子:
编码POJO时,Hessain会将POJO的类名、字段名列表写入字节流,供解码端使用。后续编码POJO字段值时,需要按照字段名列表(如上述bb、cc)的顺序来编码字段值。
POJO字段值编码
POJO字段值的tag为O,对照int32的编码格式,POJO字段值的编码格式如下:
举个例子:
可以看到,编码POJO字段值的时候,在tag后面有一个POJO结构序号。
这是Hessian的一个数据复用的小技巧。
POJO结构复用
JSON协议有一个缺点,那就是重复数据带来的存储/传输开销。举个例子:
如上图,B类型的字段名(dd、ee)在编码产物中重复出现!
Hessian希望解决这个问题,同一类型的多个POJO对象在序列化时,只需要在第一次的时候编码类名、字段名等元数据,后续可以被重复的引用、使用,无需重复编码。
如果用Hessian来编码,结果会是这样:
数据布局
数据布局详解
如上图,APojo、BPojo的字段名只会编码一次。多个BPojo对象在编码时会通过**结构引用序号(1)**来引用它。相对JSON,Hessian避免了多次编码BPojo字段名的开销。
为什么APojo的序号是1、BPojo的序号是2?
Hessain在编码过程中,每次遇到一个新的、没有处理过的新POJO类型时,会给它分配一个从0开始、单调递增的序号。
遥相呼应的,解码侧每次解码一个tag为C的POJO结构数据块时,也会按解码顺序维护好其索引序号。
四、Hessian编码细节
到现在,我们已经对Hessian编码有了一个的概括性的认识,接下来我们来看看一些值得注意的细节。
重复对象复用
A对象里有两个字段(d、e)指向同一个对象B。如果不做处理,会因为重复编码而带来不必要的开销。
相同的一个B对象,因为被两个字段重复引用,导致2次编码、产生2份数据空间占用!
如果只是有额外的开销,没有可用性问题那都还好。关键是在循环引用场景下,会因为引用成环导致递归进行对象图遍历时触发方法栈溢出!
循环引用是重复引用的特例,只要将重复引用处理掉,循环引用也就没问题了。
Hessian通过对象引用来解决这个问题。在对象图遍历过程中,遇到一个之前没有遇到过、处理过的POJO对象时,会给它分配一个从0开始、单调递增的序号。
后续再次需要序列化相同的对象时,直接跳过编码流程,将这个对象的序号写入字节流。
解码时,解码侧按相同的顺序来恢复出引用序号表,解码后续的对象引用。
小整数内联(direct)
很多编码类型,都需要在tag后再维护一个整数类型的字段。比如:
- POJO的编码tag O需要一个整数来引用POJO结构引用序号。
- 类似String的变长类型需要一个整数来标识变长数据的长度。
当字符串很短,就比如**"hi"吧,短字符串编码格式的长度字段**可能比实际字符数据还大(用4字节存储长度2),效率低下。
tag分段
Hessian将一些tag值的语义富化,让它既体现数据类型,也体现小数值。
因为tag是一个byte(int8),取值范围是0-255,每个tag标识一种特定的数据类型(int、boolean等),但是这些数据类型最多几十种,取值范围内还有很大的数值区间没有被使用,其实比较浪费。那我们就可以把这些空闲的tag值,挪作他用,提升tag数值空间利用率。
我举个例子,注意这个是参考Hessian思路的一个简单示意,具体的Tag值和Hessian无关。
长度内联
对于长度≤31的字符串,Hessian用tag同时编码类型和长度。
- 当0 <= tag <= 31 时,标识后续的数据块为字符串。
- tag的数值即为后续数据块的长度。
示例如下:
序号内联
当结构引用序号<=16时,Hessain用tag同时编码类型和序号。
1. 当0x60 <= tag <= 0x70 时,标识后续的数据块为POJO字段值。
2. tag - 0x60的值,即为POJO结构(类名+字段名)引用序号。
示例如下:
相关源码如下:
com.alibaba.com.caucho.hessian.io.Hessian2Output#writeObjectBegin
字符串编码
Hessian编码字符串的关键流程是:字符串分段+不同长度的子串使用不同的tag。
- 分段原则
字符串会被分割为若干块,每块最大长度为32768(0x8000)。前N-1块均为完整长度的子串(32768字节),使用固定tag R标识;最后一块为剩余部分,长度范围为0-32768字节,根据实际长度选择动态tag。
-
尾段tag的选择基于尾块的长度决定
- 长度≤31(0x1F):使用单字节tag 0x00-0x1F直接内联长度值。
- 32≤长度≤1023(0x3FF):使用tag 0后跟1字节长度(大端序),10bit的计数空间由tag字节和长度字节共同提供。这个地方有点绕,看下代码吧。
- 长度≥1024:使用tag S后跟2字节长度(大端序)。
-
相关源码
com.alibaba.com.caucho.hessian.io.Hessian2Output#writeString(java.lang.String)
这种设计通过减少长字符串的冗余长度标记,在保持兼容性的同时显著提升了编码效率。
整数压缩
基础编码
整数(int32)的的取值范围很大(-23^31 - 2^31),保守的编码格式会用4个byte来编码整数。
但是日常使用中,我们会大量使用小整数,比如1、31。这时候如果还用4字节编码就很不划算啦~
变长编码
Hessian根据整数的值范围,动态的选择不同的编码方式,且不同的编码方式有不同的tag:
- 单字节整数编码:类似【长度压缩】,tag中直接内联数值
适用范围:-16 到 47(共64个值)
编码方式:使用单字节,值为 value + 0x90(144)
例如:0 编码为 0x90,-1 编码为 0x8f,47 编码为 0xbf
- 双字节整数编码
适用范围:-2048 到 2047
编码方式:首字节为 0xc8 + (value >> 8),后跟一个字节存储value剩下的bit。
这种编码可以表示12bit有符号整数
- 三字节整数编码
适用范围:-262144 到 262143
编码方式:首字节为 0xd4 + (value >> 16),后跟两个字节存储 value 的高8位和低8位。
这种编码可以表示19bit有符号整数。
- 五字节整数编码
适用范围:超出上述范围的所有32位整数
编码方式:以 'I'(0x49)开头,后跟4个字节表示完整的32位整数值。
- 相关源码:
com.alibaba.com.caucho.hessian.io.Hessian2Output#writeInt
收益
-
小整数(如 0、-1)仅需 1字节 ,而传统 int32 固定4字节。
-
大整数动态扩展,避免固定长度浪费(如 1000 仅需2字节)。
其他的数值类型比如int64也有类似的机制。
五、总结
Hessian专为Java优化,采用高效二进制协议,通过对象图遍历和编码协议实现对象与字节流的转换,利用数据块标签、重复对象复用、数据压缩等机制,提升编解码效率和数据压缩率。
本文没有去展开Hessian的代码细节,而是尽可能深入浅出的介绍了Hessain的核心编码原理,以帮助读者建立对Hessian的宏观认知,从而可以更好的去理解和使用它。
尽管不同语言/生态的序列化框架选型让人眼花缭乱,但是各自需要解决的问题和解决问题的思路都大同小异;我们对Hessain原理的认识可以迁移到其他序列化框架,甚至自己写一个领域特定的序列化框架。
相关内容均为笔者走读源码整理而来,如有疏漏,欢迎指正。
参考:
- Hessian 2.0 Serialization Protocol(http://hessian.caucho.com/doc/hessian-serialization.html)
- Hessian 2.0 序列化协议(中文版)(https://www.diguage.com/post/hessian-serialization-protocol/)
- Hessian 协议解释与实战(一):布尔、日期、浮点数与整数(https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/)
- Hessian 协议解释与实战(二):长整型、二进制数据与 Null(https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/)
- Hessian 协议解释与实战(三):字符串(https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/)
- Hessian 协议解释与实战(四):数组与集合(https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/)
- Hessian 协议解释与实战(五):对象与映射(https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/)
- Hessian 源码分析(Java)(https://www.diguage.com/post/hessian-source-analysis-for-java/)
往期回顾
1. 前端日志回捞系统的性能优化实践|得物技术
2. 得物灵犀搜索推荐词分发平台演进3.0
3. R8疑难杂症分析实战:外联优化设计缺陷引起的崩溃|得物技术
4. 可扩展系统设计的黄金法则与Go语言实践|得物技术
5. 营销会场预览直通车实践|得物技术
文 / 羊羽
关注得物技术,每周更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
GrapeCity Documents V8.0 Update2 重磅发布:性能飞跃、AI 赋能与文档处理全流程升级
作为葡萄城旗下服务端文档组件库,GrapeCity Documents(简称"GcDocs")凭借跨平台、高兼容的优势,已成为 众多开发者构建文档生成、编辑、转换与管理系统的首选。 近日,GcDocs 迎来 V8.0 Update2 版本更新,聚焦 GcExcel(电子表格处理)、GcWord(文字处理)、GcPDF(PDF 处理) 三大核心产品,从性能优化、功能增强、AI 集成三大维度实现突破,完美适配企业级文档处理的复杂场景。本文将带您逐一解锁这些新特性,看看它们如何重塑文档处理效率。 一、GcExcel V8.0 Update2:性能与功能双重突破 GcExcel 作为"后端 Excel 处理引擎",始终以"兼容 Excel 全特性、处理海量数据"为核心。V8.0 Update2 通过底层算法重构,在大文件处理、复杂公式计算上实现性能跨越式提升,同时新增高频函数与交互优化,进一步缩小与桌面 Excel 的差距。 1. 性能飙升:三大场景效率翻倍 针对企业级应用中"大文件加载慢、公式计算卡、内存占用高"的痛点,GcExcel 进行专项优化,实测数据表现亮眼: (1)公式计算速度提升...
-
下一篇
9月线下活动汇总
9月13日,广州,Solar 开发者咖啡 👨💻熟悉的开发者咖啡活动回归,这次搬到了广州!这也是Solar第一次在广州举办社区活动,尝试在大湾区布局更多站点。期待开发者们的加入! 🗓️时间:9月13日 周六14:30-18:00 📝现在报名 https://luma.com/xhki98ic 分享主题: - Solana历史、POH 机制、开发差异; - Solana现在的开源生态、技术、项目; - 基于Blinks开发所用的技术、需求分析、商业模式和收获; - Solar.zens.one, Solar的项目导航 - Solana DeFi生态现状和创新 9月13–14日,杭州,RustChinaConf 2025 x Rust Global China 今年正值 Rust 诞生 10 周年 🎉 👉 讲师阵容全面升级,海外讲师议题比例高达 45%! 👉 Rust Foundation 团队将首次现场参与交流! 👉 国内大厂创业公司纷纷加入,了解 Rust 语言的最近进展 👉 现场设有Rust 十周年庆典与特别大礼包 🔎 查看完整议程: https://rus...
相关文章
文章评论
共有0条评论来说两句吧...