- 在进行序列化操作之前,我们还对系统进行压测,通过
jvisualvm分析cpu,线程,垃圾回收情况等;运用火焰图async-profiler分析系统性能,找出程序中占用CPU资源时间最长的代码块。 - 代码放置GitHub:https://github.com/nateshao/leetcode/tree/main/source-code/src/main/java/com/nateshao/source/code/serializable
1.什么是序列化与反序列化?
聊到序列化与反序列化,先看看这个这个是什么或者是干嘛的

定义:序列化是指把
对象转换为字节序列的过程;反序列化是指把字节序列恢复为对象的过程;
一般都会知道有两个目的:对象持久化和网络传输。
- 对象持久化。实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上;
- 网络传输。我们知道网络传输的是字节序列,那么利用序列化实现远程通信,即在网络上传递对象的字节序列。 (序列化与反序列化则实现了 进程通信间的对象传送,发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象)
总结:序列化的目的是将对象变成字节序列,这样一来方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,另外字节序列也更便于网络运输和传播
2.选择序列化技术有哪些维度
这里并不是说哪个是最好,只能说是各有千秋,所以面对不同的背景采用不同的技术方案。
在同等情况下,编码后的字节数组越大,存储占空间,存储硬件成本高,网络传输时也占带宽,导致系统的吞吐量降低。
- 开发工作量和难度。工作量越少越好对吧,发挥的价值最大。
- 性能:需要考虑性能需求,序列化的速度和性能越高越好。
- 是否支持跨语言,支持的语言种类是否丰富。
- 安全性:对于安全性的需要考量。JDK序列化可能有卡死线程的漏洞。
- 占用空间大小:序列化的结果所占用的空间大小,序列化后的字节数据通常会持久化到硬盘(占用存储资源)或者在网络上传输给其他服务器(占用带宽资源),这个指标当然是越小越好。
- 可维护性:技术流行程序,越流行的技术可维护性就越高,维护成本会越低。
3.为什么采用字节序列MessagePack,有什么依据?
这个要结合当时的项目背景了。当时的项目痛点是:
- 老项目的结构调整比较大,交易性能上不去,所以先采取细节方面的优化。MessagePack是满足性能要求。
- 老系统业务变化不多,MessagePack是满足要求。
- 序列化反序列化效率高(比json快一倍),文件体积小,比json小一倍。MessagePack是满足要求
所以,存在MessagePack也有不好的地方,如果是针对业务变化比较多,那就不适合,需要比较不同的版本,然后选择不同版本去解析。
我在京东內部文章有看到,引用的MessagePack反序列化出现了一些线上的问题,原因是:新增了一些字段导致反序列化失败。
这里对比了JDK(不支持跨语言),JSON,Protobuf,MessagePack几种序列化的方式。
当然,还有其他的XML、Hessian、Kryo(不支持跨语言)、FST(不支持跨语言)Thrift等。
4.JDK序列化
JDK序列化是Java默认自带的序列化方式。在Java中,一个对象要想实现序列化,实现Serializable接口或者Externalizable接口即可。其中Externalizable接口继承自Serializable接口。
1public class Person implements Serializable { 2 private static final long serialVersionUID = 1L; 3 private String name = null; 4 private Integer age = null; 5 private SerializeDemo01.Sex sex; 6 //.... 7}
serialVersionUID版本控制的作用
- 序列化的时候
serialVersionUID也会被写入二进制序列 - 反序列化时会检查
serialVersionUID是否和当前类的serialVersionUID一致。不一致则会抛出InvalidClassException异常(序列化之后给类增加字段再反序列化的时候就会报错)
serialVersionUID必须保证实体类在序列化前后严格一致,否则将会导致无法反序列化。
JDK默认的序列化机制。需要实现java.io.Serializable接口
- 序列化后的码流太大:jdk序列化机制编码后的二进制数组大小竟然是二进制编码的5.29倍。
- 不支持跨语言平台调用, 性能较差(序列化后字节数组体积大)
- 序列化性能太低:java序列化的性能只有二进制编码的6.17%左右,Java原生序列化的性能实在太差。
JDK默认的序列化机制很差。所以我们通常不会选择Java默认序列化这种
5.JSON序列化
json格式也是常见的一种,但是在json在解析的时候非常耗时,而且json结构非常占内存。JSON不适合存数字,特别是DOUBLE
这里基于Fastjson ,加载maven依赖
1<dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>fastjson</artifactId> 4 <version>1.2.70</version> 5</dependency>
序列化对象
利用JSON.toJSONString方法序列化对象:
UserVO user = ...;String text = JSON.toJSONString(user);
序列化数组
利用JSON.toJSONString方法序列化数组:
UserVO[] users = ...;String text = JSON.toJSONString(users);
序列化集合
利用JSON.toJSONString方法序列化集合(继承至Collection,比如List、Set等集合):
List<UserVO> userList = ...;String text = JSON.toJSONString(userList);
序列化映射
利用JSON.toJSONString方法序列化映射:
Map<Long, UserVO> userMap = ...;String text = JSON.toJSONString(userMap, SerializerFeature.MapSortField);
其中,为了保证每次序列化的映射字符串一致,需要指定序列化参数MapSortField进行排序。
序列化模板对象
利用JSON.toJSONString方法序列化模板对象:
Result<UserVO> result = ...;String text = JSON.toJSONString(result);
代码
1package com.nateshao.source.code.serializable.json_Serializable.serializable; 2 3import com.alibaba.fastjson.annotation.JSONField; 4/** 5 * @date Created by 邵桐杰 on 2023/2/25 17:11 6 * @微信公众号 千羽的编程时光 7 * @博客 https://nateshao.gitlab.io 8 * @GitHub https://github.com/nateshao 9 * Description: 10 */ 11public class User { 12 /** 13 * @JSONField 作用:自定义对象属性所对应的 JSON 键名 14 * @JSONField 的作用对象: 15 * 1. Field 16 * 2. Setter 和 Getter 方法 17 * 注意: 18 * 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。 19 * 2. 若没有 @JSONField 注解,则直接使用属性名。 20 */ 21 @JSONField(name = "NAME") 22 private String name; 23 @JSONField(name = "AGE") 24 private int age; 25 26 public User(String name, int age) { 27 this.name = name; 28 this.age = age; 29 } 30 31 public String getName() { 32 return name; 33 } 34 35 public void setName(String name) { 36 this.name = name; 37 } 38 39 public int getAge() { 40 return age; 41 } 42 43 public void setAge(int age) { 44 this.age = age; 45 } 46}
序列化ObjectTest
1package com.nateshao.source.code.serializable.json_Serializable.serializable; 2 3import com.alibaba.fastjson.JSON; 4import org.junit.jupiter.api.BeforeAll; 5import org.junit.jupiter.api.DisplayName; 6import org.junit.jupiter.api.Test; 7 8import java.util.ArrayList; 9import java.util.HashMap; 10import java.util.List; 11import java.util.Map; 12 13/** 14 * @date Created by 邵桐杰 on 2023/2/25 17:12 15 * @微信公众号 千羽的编程时光 16 * @博客 https://nateshao.gitlab.io 17 * @GitHub https://github.com/nateshao 18 * Description: 19 */ 20public class ObjectTest { 21 private static List<User> userList = new ArrayList<User>(); 22 23 @BeforeAll 24 public static void setUp() { 25 userList.add(new User("千羽", 18)); 26 userList.add(new User("千寻", 19)); 27 } 28 29 @DisplayName("序列化对象") 30 @Test 31 public void testObjectToJson() { 32 String userJson = JSON.toJSONString(userList.get(0)); 33 System.out.println(userJson); // {"AGE":18,"NAME":"千羽"} 34 } 35 36 @DisplayName("序列化集合") 37 @Test 38 public void testListToJson() { 39 String userListJson = JSON.toJSONString(userList); 40 System.out.println(userListJson); // [{"AGE":18,"NAME":"千羽"},{"AGE":19,"NAME":"千寻"}] 41 } 42 43 @DisplayName("序列化数组") 44 @Test 45 public void testArrayToJson() { 46 User[] userArray = new User[5]; 47 userArray[0] = new User("zhangsan", 20); 48 userArray[1] = new User("lisi", 21); 49 String userArrayJson = JSON.toJSONString(userArray); 50 System.out.println(userArrayJson); // [{"AGE":20,"NAME":"zhangsan"},{"AGE":21,"NAME":"lisi"},null,null,null] 51 } 52 53 @DisplayName("序列化映射") 54 @Test 55 public void testMapToJson() { 56 Map<Integer, User> userMap = new HashMap<Integer, User>(); 57 userMap.put(1, new User("xiaotie", 10)); 58 userMap.put(2, new User("xiaoliu", 11)); 59 String userMapJson = JSON.toJSONString(userMap); 60 System.out.println(userMapJson); // {1:{"AGE":10,"NAME":"xiaotie"},2:{"AGE":11,"NAME":"xiaoliu"}} 61 } 62}
反序列化UN_ObjectTest
1package com.nateshao.source.code.serializable.json_Serializable.un_serializable; 2 3import com.alibaba.fastjson.JSON; 4import com.alibaba.fastjson.TypeReference; 5import org.junit.jupiter.api.DisplayName; 6import org.junit.jupiter.api.Test; 7 8import java.util.Arrays; 9import java.util.List; 10import java.util.Map; 11import java.util.Set; 12 13/** 14 * @date Created by 邵桐杰 on 2023/2/25 17:16 15 * @微信公众号 千羽的编程时光 16 * @博客 https://nateshao.gitlab.io 17 * @GitHub https://github.com/nateshao 18 * Description: 19 */ 20public class UN_ObjectTest { 21 @DisplayName("反序列化对象") 22 @Test 23 public void testJsonToObject() { 24 String text = "{"age":18,"name":"千羽"}"; 25 User user = JSON.parseObject(text, User.class); 26 System.out.println(user); // User{name='千羽', age=18} 27 } 28 29 @DisplayName("反序列化数组") 30 @Test 31 public void testJsonToArray() { 32 String text = "[{"age":18,"name":"千羽"}, {"age":19,"name":"千寻"}]"; 33 User[] users = JSON.parseObject(text, User[].class); 34 System.out.println(Arrays.toString(users)); // [User{name='千羽', age=18}, User{name='千寻', age=19}] 35 } 36 37 @DisplayName("反序列化集合") 38 @Test 39 public void testJsonToCollection() { 40 String text = "[{"age":18,"name":"千羽"}, {"age":19,"name":"千寻"}]"; 41 // List 集合 42 List<User> userList = JSON.parseArray(text, User.class); 43 System.out.println(Arrays.toString(userList.toArray())); // [User{name='千羽', age=18}, User{name='千寻', age=19}] 44 // Set 集合 45 Set<User> userSet = JSON.parseObject(text, new TypeReference<Set<User>>() { 46 }); 47 System.out.println(Arrays.toString(userSet.toArray())); // [User{name='千寻', age=19}, User{name='千羽', age=18}] 48 } 49 50 @DisplayName("反序列化映射") 51 @Test 52 public void testJsonToMap() { 53 String text = "{1:{"age":18,"name":"千羽"}, 2:{"age":19,"name":"千寻"}}"; 54 Map<Integer, User> userList = JSON.parseObject(text, new TypeReference<Map<Integer, User>>() { 55 }); 56 for (Integer i : userList.keySet()) { 57 System.out.println(userList.get(i)); 58 } 59 /* 60 User{name='千羽', age=18} 61 User{name='千寻', age=19} 62 */ 63 } 64}
json序列化总结
-
好处
- 简单易用开发成本低
- 跨语言
- 轻量级数据交换
- 非冗长性(对比xml标签简单括号闭环)
-
缺点
- 体积大,影响高并发
- 无版本检查,自己做兼容
- 片段的创建和验证过程比一般的XML复杂
- 缺乏命名空间导致信息混合
6.Protobuf
Protobuf是谷歌推出的,是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于通信协议、数据存储等。序列化后体积小,一般用于对传输性能有较高要求的系统。前期需要额外配置环境变量,学习他的语法也是需要时间。
但是要使用Protobuf会相对来说麻烦一些,因为他有自己的语法,有自己的编译器,如果需要用到的话必须要去投入成本在这个技术的学习中。
Protobuf有个缺点就是传输的每一个类的结构都要生成相对应的proto文件,如果某个类发生修改,还要重新生成该类对应的proto文件。另外,Protobuf对象冗余,字段很多,生成的类较大,占用空间。维护成本较高。
1// 声明语法,不显示声明默认是2.0的语法。 2syntax = "proto3"; 3// 指定模板类的包路径 4option java_package = "com.test.basic.java.serialize.proto.dto"; 5// 指定模板类的名称,名称必须是有实际业务意义的 6option java_outer_classname = "UserProto"; 7 8// 定义用户对象 9message User { 10 // 名字 11 string name = 1; 12 // 年龄 13 int32 age = 2; 14} 15// 声明语法,不显示声明默认是2.0的语法。 16syntax = "proto3"; 17// 指定模板类的包路径 18option java_package = "com\nateshao\source\code\serializable\protobuf_Serializable\User.proto"; 19// 指定模板类的名称,名称必须是有实际业务意义的 20option java_outer_classname = "UserProto"; 21 22// 定义用户对象 23message User { 24 // 名字 25 string name = 1; 26 // 年龄 27 int32 age = 2; 28}
7.MessagePack(推荐)

- 序列化反序列化效率高(比json快一倍),文件体积小,比json小一倍。
- 兼容json数据格式
- 跨语言,多语言支持(超多)

不好的地方:
- 缺乏复杂模型支持。msgpack对复杂的数据类型(List、Map)支持的不够,序列化没有问题,但是反序列化回来就很麻烦,尤其是对于java开发人员。
- 维护成本较高。msgpack通过value的顺序来定位属性的,需要在不同的语言中都要维护同样的模型以及模型中属性的顺序。
- 不支持模型嵌套。msgpack无法支持在模型中包含和嵌套其他自定义的模型(如weibo模型中包含comment的列表)。
上手
通过配置msgpack的maven依赖
1<dependency> 2 <groupId>org.msgpack</groupId> 3 <artifactId>msgpack</artifactId> 4 <version>0.6.12</version> 5</dependency>
封装MsgPackTemplate抽象类,对原有msgpack进行二次加工,比如int,Short,Byte,BigDecimal进行read和write处理
MsgPackTemplate.java
1package com.nateshao.source.code.serializable.msgpack_Serializable; 2 3import java.io.IOException; 4import java.math.BigDecimal; 5import java.util.Date; 6 7import org.msgpack.packer.Packer; 8import org.msgpack.template.CharacterTemplate; 9import org.msgpack.template.Template; 10import org.msgpack.unpacker.Unpacker; 11/** 12 * @date Created by 邵桐杰 on 2023/2/25 23:11 13 * @微信公众号 千羽的编程时光 14 * @博客 https://nateshao.gitlab.io 15 * @GitHub https://github.com/nateshao 16 * Description: 17 */ 18public abstract class MsgPackTemplate <T> implements Template<T> { 19 20 public void write(Packer pk, T v) throws IOException { 21 write(pk, v, false); 22 } 23 24 public T read(Unpacker u, T to) throws IOException { 25 return read(u, to, false); 26 } 27 28 public BigDecimal readBigDecimal(Unpacker u) throws IOException { 29 if (u.trySkipNil()) { 30 return null; 31 } 32 33 String temp = u.readString(); 34 if (temp != null) { 35 return new BigDecimal(temp); 36 } 37 return null; 38 } 39 40 public Date readDate(Unpacker u) throws IOException { 41 if (u.trySkipNil()) { 42 return null; 43 } 44 45 long temp = u.readLong(); 46 if (temp > 0) { 47 return new Date(temp); 48 } 49 return null; 50 } 51 52 public String readString(Unpacker u) throws IOException { 53 if (u.trySkipNil()) { 54 return null; 55 } 56 return u.readString(); 57 } 58 59 public Integer readInteger(Unpacker u) throws IOException { 60 if (u.trySkipNil()) { 61 return null; 62 } 63 return u.readInt(); 64 } 65 66 public Byte readByte(Unpacker u) throws IOException { 67 if (u.trySkipNil()) { 68 return null; 69 } 70 return u.readByte(); 71 } 72 73 public Short readShort(Unpacker u) throws IOException { 74 if (u.trySkipNil()) { 75 return null; 76 } 77 return u.readShort(); 78 } 79 80 public Float readFloat(Unpacker u) throws IOException { 81 if (u.trySkipNil()) { 82 return null; 83 } 84 return u.readFloat(); 85 } 86 87 public Double readDouble(Unpacker u) throws IOException { 88 if (u.trySkipNil()) { 89 return null; 90 } 91 return u.readDouble(); 92 } 93 94 public Character readCharacter(Unpacker u) throws IOException { 95 if (u.trySkipNil()) { 96 return null; 97 } 98 return u.read(CharacterTemplate.getInstance()); 99 100 } 101 102 public Long readLong(Unpacker u) throws IOException { 103 if (u.trySkipNil()) { 104 return null; 105 } 106 return u.readLong(); 107 } 108 109 public Boolean readBoolean(Unpacker u) throws IOException { 110 if (u.trySkipNil()) { 111 return null; 112 } 113 return u.readBoolean(); 114 } 115 116 117 118 119 public void writeBigDecimal(Packer pk, BigDecimal v) throws IOException { 120 if (v == null) { 121 pk.writeNil(); 122 } else { 123 pk.write(v.toString()); 124 } 125 } 126 127 public void writeDate(Packer pk, Date v) throws IOException { 128 if (v == null) { 129 pk.writeNil(); 130 } else { 131 pk.write(v.getTime()); 132 } 133 } 134 135 public void writeString(Packer pk, String v) throws IOException { 136 if (v == null) { 137 pk.writeNil(); 138 } else { 139 pk.write(v); 140 } 141 } 142 143 public void writeInt(Packer pk, int v) throws IOException { 144 pk.write(v); 145 } 146 147 public void writeInteger(Packer pk, Integer v) throws IOException { 148 if (v == null) { 149 pk.writeNil(); 150 } else { 151 pk.write(v.intValue()); 152 } 153 } 154 155 public void writeByte(Packer pk, Byte v) throws IOException { 156 if (v == null) { 157 pk.writeNil(); 158 } else { 159 pk.write(v.byteValue()); 160 } 161 } 162 163 public void writeShort(Packer pk, Short v) throws IOException { 164 if (v == null) { 165 pk.writeNil(); 166 } else { 167 pk.write(v.shortValue()); 168 } 169 } 170 171 public void writeFloat(Packer pk, Float v) throws IOException { 172 if (v == null) { 173 pk.writeNil(); 174 } else { 175 pk.write(v.floatValue()); 176 } 177 } 178 179 public void writeDouble(Packer pk, Double v) throws IOException { 180 if (v == null) { 181 pk.writeNil(); 182 } else { 183 pk.write(v.doubleValue()); 184 } 185 } 186 187 public void writeCharacter(Packer pk, Character v) throws IOException { 188 if (v == null) { 189 pk.writeNil(); 190 } else { 191 pk.write(v.charValue()); 192 } 193 194 } 195 196 public void writeLong(Packer pk, Long v) throws IOException { 197 if (v == null) { 198 pk.writeNil(); 199 } else { 200 pk.write(v.longValue()); 201 } 202 } 203 204 public void writeLong(Packer pk, long v) throws IOException { 205 pk.write(v); 206 } 207 208 public void writeBoolean(Packer pk, Boolean v) throws IOException { 209 if (v == null) { 210 pk.writeNil(); 211 } else { 212 pk.write(v.booleanValue()); 213 } 214 } 215}
然后针对实体类进行读和写的序列化与反序列化操作。
User.java
1package com.nateshao.source.code.serializable.msgpack_Serializable; 2@Data 3@ToString 4@AllArgsConstructor 5@NoArgsConstructor 6public class User { 7 private static final long serialVersionUID = 4719921525393585541L; 8 protected String id; 9 private String userID; 10 private String type; 11 private BigDecimal rate; 12 private Date createTime; 13 private Date UpdateTime; 14}
UserTemplate.java
1package com.nateshao.source.code.serializable.msgpack_Serializable; 2 3import org.msgpack.packer.Packer; 4import org.msgpack.unpacker.Unpacker; 5 6import java.io.IOException; 7 8/** 9 * protected String id; 10 * private String userID; 11 * private String type; 12 * private BigDecimal rate; 13 * private Date createTime; 14 * private Date UpdateTime; 15 */ 16public class UserTemplate extends MsgPackTemplate<User> { 17 public void write(Packer packer, User user, boolean b) throws IOException { 18 if (user == null) { 19 packer.write(user); 20 return; 21 } 22 // 字段保持一致 23 writeString(packer, user.id); 24 writeString(packer, user.getUserID()); 25 writeString(packer, user.getType()); 26 writeBigDecimal(packer, user.getRate()); 27 writeDate(packer, user.getCreateTime()); 28 writeDate(packer, user.getUpdateTime()); 29 30 } 31 32 public User read(Unpacker unpacker, User user, boolean req) throws IOException { 33 if (!req && unpacker.trySkipNil()) { 34 return null; 35 } 36 if (unpacker == null) return new User(); 37 user.setId(readString(unpacker)); 38 user.setUserID(readString(unpacker)); 39 user.setType(readString(unpacker)); 40 user.setRate(readBigDecimal(unpacker)); 41 user.setCreateTime(readDate(unpacker)); 42 user.setUpdateTime(readDate(unpacker)); 43 return user; 44 } 45}
参考文献:
- 官网:MessagePack: It's like JSON. but fast and small:https://msgpack.org/)
- msgpack-java:https://github.com/msgpack/msgpack-java
- MessagePack for C#译文:快速序列化组件MessagePack介绍 - 晓晨Master - 博客园:https://www.cnblogs.com/stulzq/p/8039933.html
- 原理分析和使用:https://www.jianshu.com/p/8c24bef40e2f
- https://blog.csdn.net/wzngzaixiaomantou/article/details/123031809
- https://blog.csdn.net/weixin_44299027/article/details/129050484
- https://www.cnblogs.com/juno3550/p/15431623.html
- Protobuf:https://blog.csdn.net/wxw1997a/article/details/116755542
作者:京东零售 邵桐杰
来源:京东云开发者社区
