VLAct:具身智能的动作语义骨干网络
2026/10/14 16:26:49
序列化是分布式系统的基石。从 Java 原生序列化到 Protobuf,性能差距可达数十倍。本文拆解各序列化方案的底层逻辑和选型指南。
序列化 (Serialization): 对象 → 字节序列 → 存储/传输 反序列化 (Deserialization): 字节序列 → 对象 ┌───────────┐ 序列化 ┌───────────┐ 反序列化 ┌───────────┐ │ Java对象 │──────────▶│ 字节流 │──────────▶│ Java对象 │ │ (内存) │ │ (网络/磁盘) │ │ (内存) │ └───────────┘ └───────────┘ └───────────┘ 应用场景: ├── RPC 通信 (Dubbo/gRPC) ├── 缓存存储 (Redis Memcached) ├── 消息队列 (Kafka/RocketMQ) ├── 持久化 (数据库 BLOB) └── 深拷贝 (对象复制)// 实现 Serializable 接口publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateStringname;privateintage;// transient: 不参与序列化privatetransientStringpassword;// 自定义序列化逻辑privatevoidwriteObject(ObjectOutputStreamoos)throwsIOException{oos.defaultWriteObject();oos.writeObject(encrypt(password));// 加密后写入}privatevoidreadObject(ObjectInputStreamois)throwsIOException,ClassNotFoundException{ois.defaultReadObject();this.password=decrypt((String)ois.readObject());}privateStringencrypt(Strings){returnBase64.encode(s);}privateStringdecrypt(Strings){returnBase64.decode(s);}}// 使用ByteArrayOutputStreambos=newByteArrayOutputStream();ObjectOutputStreamoos=newObjectOutputStream(bos);oos.writeObject(user);byte[]bytes=bos.toByteArray();// 序列化结果ObjectInputStreamois=newObjectInputStream(newByteArrayInputStream(bytes));Useruser2=(User)ois.readObject();// 反序列化// 1. 体积大 — 包含类信息、字段名、类描述等大量元数据// 序列化一个 {"name":"Tom","age":20} 的对象:// Java序列化: ~150 bytes (含类名、字段名、serialVersionUID等)// JSON: ~25 bytes// Protobuf: ~10 bytes// 2. 性能差 — 大量反射调用// Java序列化 vs JSON vs Protobuf (相对耗时):// Java: 10x// JSON: 3x// Protobuf: 1x// 3. 安全漏洞 — 反序列化可执行任意代码// 致命漏洞: Apache Commons Collections 反序列化漏洞// 攻击者构造恶意字节流 → readObject 时执行任意命令// 4. 跨语言不兼容 — 只有 Java 能反序列化// 5. API 不友好 — 无法只序列化部分字段Java 序列化字节布局: ┌─────────────────────────────────────────────────┐ │ 魔数 (STREAM_MAGIC) 2 bytes │ │ 版本 (STREAM_VERSION) 2 bytes │ │ TC_OBJECT 标志 1 byte │ │ 类描述 (TC_CLASSDESC) │ │ ├── 类名长度 2 bytes │ │ ├── 类名 "com.example.User" ~20 bytes │ │ ├── serialVersionUID 8 bytes │ │ ├── 标志位 1 byte │ │ ├── 字段数量 2 bytes │ │ └── 字段描述 (字段名+类型) ~50 bytes │ │ 字段值 │ │ ├── "Tom" ~8 bytes │ │ └── 20 4 bytes │ │ 结束标志 │ └─────────────────────────────────────────────────┘ 总计: ~100+ bytes (而实际数据只有 8 bytes)// Jackson (最常用)ObjectMappermapper=newObjectMapper();// 序列化Stringjson=mapper.writeValueAsString(user);// {"name":"Tom","age":20}// 反序列化Useruser=mapper.readValue(json,User.class);// 高级配置mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);mapper.setDateFormat(newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"));// GsonGsongson=newGsonBuilder().setDateFormat("yyyy-MM-dd").create();Stringjson=gson.toJson(user);Useruser=gson.fromJson(json,User.class);// Fastjson2 (高性能)Stringjson=JSON.toJSONString(user);Useruser=JSON.parseObject(json,User.class);JSON 序列化特点: 优点: ├── 可读性强 (文本格式) ├── 跨语言通用 ├── 生态成熟 (浏览器/API) └── 调试方便 缺点: ├── 体积较大 (字段名+引号+括号) ├── 性能中等 (文本解析) ├── 无类型信息 (反序列化需指定类型) └── 数字精度问题 (long → JS number 丢失)// user.proto syntax = "proto3"; message User { string name = 1; int32 age = 2; string email = 3; repeated string tags = 4; }protoc--java_out=./src user.proto// 构建对象UserProto.Useruser=UserProto.User.newBuilder().setName("Tom").setAge(20).setEmail("tom@example.com").addTags("java").addTags("backend").build();// 序列化byte[]bytes=user.toByteArray();// 反序列化UserProto.Useruser2=UserProto.User.parseFrom(bytes);Protobuf 编码原理: 1. Varint 编码 (变长整数) 小数字用更少字节: 1 → 0x01 (1 byte) 300 → 0xAC 0x02 (2 bytes) 而不是固定 4 bytes 2. 字段用 Tag (field_number + wire_type) 标识 不存储字段名,用数字编号代替: field_number=1, type=string → Tag = (1 << 3) | 2 = 0x0A 3. 字段省略 proto3 默认值字段不编码: age=0 → 不写入字节流 (0 bytes) 4. 二进制格式 无文本解析开销 对比同一个 User: Java序列化: ~150 bytes (含类名、字段名等元数据) JSON: ~45 bytes (含字段名、引号) Protobuf: ~15 bytes (仅字段编号+值)Protobuf 编码示例: message: { name="Tom", age=20, email="" } 字段1 (name, type=2/string): Tag=0x0A, Len=3, Value="Tom" 字段2 (age, type=0/varint): Tag=0x10, Value=20 字段3 (email, 默认值): 省略不编码 字节流: 0A 03 54 6F 6D 10 14 总计: 7 bytes! JSON 等价: {"name":"Tom","age":20} → 23 bytes| 序列化方案 | 体积 | 性能 | 跨语言 | 可读性 | 类型安全 | 典型场景 |
|---|---|---|---|---|---|---|
| Java Serializable | 最大 | 最慢 | 仅Java | 不可读 | 强 | 不推荐 |
| JSON (Jackson) | 中 | 中 | 全平台 | 可读 | 弱 | REST API |
| Protobuf | 最小 | 最快 | 全平台 | 不可读 | 强 | gRPC/RPC |
| Kryo | 小 | 快 | 仅JVM | 不可读 | 强 | 内部RPC |
| Hessian | 较小 | 较快 | Java/C++ | 不可读 | 强 | Dubbo |
| Avro | 小 | 快 | 全平台 | 不可读 | 强 | 大数据 |
| MessagePack | 小 | 快 | 全平台 | 不可读 | 中 | 消息队列 |
序列化性能对比 (以 Protobuf 为基准): 体积 (越小越好): Protobuf ██ 1x Kryo ██▎ 1.2x MessagePack █▌ 1.5x JSON ████ 3x Hessian █████ 4x Java ████████████ 10x+ 耗时 (越小越好): Protobuf ██ 1x Kryo ██▎ 1.3x MessagePack ██▌ 1.5x JSON ████ 3x Hessian ████▌ 3.5x Java ██████████ 10x序列化选型决策树: 需要跨语言? ├── 是 → 需要人类可读? │ ├── 是 → JSON (Jackson/Gson) │ │ 适用: REST API, 配置文件, 调试 │ └── 否 → 对性能要求极高? │ ├── 是 → Protobuf │ │ 适用: gRPC, 微服务内部通信 │ └── 否 → MessagePack/Avro │ 适用: 消息队列, 大数据 │ └── 否 (仅 Java) ├── 性能敏感? → Kryo │ 适用: 内部 RPC, 缓存 └── 兼容旧系统? → Hessian 适用: Dubbo 默认序列化 特殊场景: 大数据: Avro (Schema 内嵌, Spark/Kafka 原生支持) 极致体积: Protobuf (字段编号替代字段名) 调试友好: JSON (文本可读) 安全敏感: 避免 Java Serializable (反序列化漏洞)// 发送端publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateStringname;}// 接收端修改了类publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=2L;// 改了!privateStringname;privateintage;// 新增字段}// 反序列化报错: java.io.InvalidClassException - local class incompatible// 致命: Java 原生反序列化可执行任意代码ObjectInputStreamois=newObjectInputStream(inputStream);Objectobj=ois.readObject();// 危险! 可触发任意命令执行// 防御:// 1. 使用白名单反序列化 (ObjectInputFilter, JDK 9+)ObjectInputStreamois=newObjectInputStream(inputStream);ois.setObjectInputFilter(filterInfo->filterInfo.serialClass()==User.class?ObjectInputFilter.Status.ALLOWED:ObjectInputFilter.Status.REJECTED);// 2. 避免使用 Java 原生序列化,改用 JSON/Protobuf// 后端返回 Long 类型{"id":123456789012345678}// ↑ JS 中超过 Number.MAX_SAFE_INTEGER// JS 接收:// id = 123456789012345680 (末两位丢失!)// 解决方案:// 1. 后端 Long 转 String 返回@JsonSerialize(using=ToStringSerializer.class)privateLongid;// 2. 全局配置 (Jackson)objectMapper.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS,true);// 向后兼容原则: // 1. 字段编号一旦使用不能修改 // 2. 删除字段要保留 reserved // 3. 新增字段用新的编号 message User { reserved 3; // 字段3已删除,保留编号避免复用 string name = 1; int32 age = 2; string email = 4; // 新增字段用编号4 // int32 status = 3; // ❌ 错误: 不能复用已删除的编号 }体积上 Protobuf 比 JSON 小 3-5 倍(字段编号替代字段名 + Varint 编码)。性能上 Protobuf 比 JSON 快 3-10 倍(二进制解析 vs 文本解析)。
序列化演进链路: Java Serializable (慢、大、不安全、仅Java) ↓ 追求可读性 JSON (Jackson/Gson/Fastjson) ↓ 追求性能 Protobuf (二进制、跨语言、极致体积和速度) ↓ 内部场景 Kryo/Hessian (JVM 内高效序列化) 选型建议: REST API: JSON (Jackson) gRPC/RPC: Protobuf 内部通信: Kryo/Protobuf 大数据: Avro 消息队列: Protobuf/MessagePack 缓存: Kryo/JSON (可读性优先)序列化的选型核心是平衡体积、性能、跨语言、可读性四个维度。没有银弹,只有最适合场景的选择。