Java序列化的几种方式

本文探讨了Java对象序列化的两种常见方式:JSON和Google ProtoBuf。通过示例展示了如何使用Gson将UserVo对象转换为JSON字符串,以及ProtoBuf的序列化与反序列化过程。JSON易于阅读,跨语言,但字节数较多;而ProtoBuf字节数少,适合网络传输,但需要额外的代码生成步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有两种:一是把对象包装成JSON字符串传输,二是采用java对象的序列化和反序列化。随着Google工具protoBuf的开源,protobuf也是个不错的选择。对JSON,Object Serialize,
ProtoBuf 做个对比。

定义一个待传输的对象UserVo:

Java代码  收藏代码
  1. public class UserVo{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.       
  6.     private List<UserVo> friends;  
  7. ……  
  8. }  

 初始化UserVo的实例src:

Java代码  收藏代码
  1. UserVo src = new UserVo();  
  2. src.setName("Yaoming");  
  3. src.setAge(30);  
  4. src.setPhone(13789878978L);  
  5.       
  6. UserVo f1 = new UserVo();  
  7. f1.setName("tmac");  
  8. f1.setAge(32);  
  9. f1.setPhone(138999898989L);  
  10. UserVo f2 = new UserVo();  
  11. f2.setName("liuwei");  
  12. f2.setAge(29);  
  13. f2.setPhone(138999899989L);  
  14.           
  15. List<UserVo> friends = new ArrayList<UserVo>();  
  16. friends.add(f1);  
  17. friends.add(f2);  
  18. src.setFriends(friends);  

JSON格式

采用Google的gson-2.2.2.jar 进行转义

Java代码  收藏代码
  1. Gson gson = new Gson();  
  2. String json = gson.toJson(src);  

 得到的字符串:

Js代码  收藏代码
  1. {"name":"Yaoming","age":30,"phone":13789878978,"friends":[{"name":"tmac","age":32,"phone":138999898989},{"name":"liuwei","age":29,"phone":138999899989}]}  

 字节数为153

Json的优点:明文结构一目了然,可以跨语言,属性的增加减少对解析端影响较小。缺点:字节数过多,依赖于不同的第三方类库。

 

Object Serialize

UserVo实现Serializalbe接口,提供唯一的版本号:

Java代码  收藏代码
  1. public class UserVo implements Serializable{  
  2.   
  3.     private static final long serialVersionUID = -5726374138698742258L;  
  4.     private String name;  
  5.     private int age;  
  6.     private long phone;  
  7.       
  8.     private List<UserVo> friends;  

 

序列化方法:

Java代码  收藏代码
  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  

 字节数是238

 

反序列化:

Java代码  收藏代码
  1. ObjectInputStream ois = new ObjectInputStream(fis);  
  2. vo = (UserVo) ois.readObject();  
  3. ois.close();  
  4. fis.close();  

Object Serializalbe 优点:java原生支持,不需要提供第三方的类库,使用比较简单。缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。 

对象在进行序列化和反序列化的时候,必须实现Serializable接口,但并不强制声明唯一的serialVersionUID

是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。我们来做个测试:

  思路一

把UserVo中的serialVersionUID去掉,序列化保存。反序列化的时候,增加或减少个字段,看是否成功。

Java代码  收藏代码
  1. public class UserVo implements Serializable{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.       
  6.     private List<UserVo> friends;  

 

保存到文件中:

Java代码  收藏代码
  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  
  8.   
  9. FileOutputStream fos = new FileOutputStream(dataFile);  
  10. fos.write(b);  
  11. fos.close();  

 

增加或者减少字段后,从文件中读出来,反序列化:

Java代码  收藏代码
  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. ObjectInputStream ois = new ObjectInputStream(fis);  
  3. vo = (UserVo) ois.readObject();  
  4. ois.close();  
  5. fis.close();  

 

结果:抛出异常信息

Java代码  收藏代码
  1. Exception in thread "main" java.io.InvalidClassException: serialize.obj.UserVo; local class incompatible: stream classdesc serialVersionUID = 3305402508581390189, local class serialVersionUID = 7174371419787432394  
  2.     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)  
  3.     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)  
  4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)  
  5.     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)  
  6.     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)  
  7.     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)  
  8.     at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)  
  9.     at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)  

  思路二

eclipse指定生成一个serialVersionUID,序列化保存,修改字段后反序列化

略去代码

结果:反序列化成功

  结论

如果没有明确指定serialVersionUID,序列化的时候会根据字段和特定的算法生成一个serialVersionUID,当属性有变化时这个id发生了变化,所以反序列化的时候就会失败。抛出“本地class的唯一id和流中class的唯一id不匹配”。

 

jdk文档关于serialVersionUID的描述:

写道
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,数组类没有匹配 serialVersionUID 值的要求。

 

Google ProtoBuf

protocol buffers 是google内部得一种传输协议,目前项目已经开源(http://code.google.com/p/protobuf/)。它定义了一种紧凑得可扩展得二进制协议格式,适合网络传输,并且针对多个语言有不同得版本可供选择。

以protobuf-2.5.0rc1为例,准备工作:

下载源码,解压,编译,安装

Shell代码  收藏代码
  1. tar zxvf protobuf-2.5.0rc1.tar.gz  
  2. ./configure  
  3. ./make  
  4. ./make install  

 测试:

Shell代码  收藏代码
  1. MacBook-Air:~ ming$ protoc --version  
  2. libprotoc 2.5.0  

 安装成功!进入源码的java目录,用mvn工具编译生成所需得jar包,protobuf-java-2.5.0rc1.jar

 

1、编写.proto文件,命名UserVo.proto 

Text代码  收藏代码
  1. package serialize;  
  2.   
  3. option java_package = "serialize";  
  4. option java_outer_classname="UserVoProtos";  
  5.   
  6. message UserVo{  
  7.     optional string name = 1;  
  8.     optional int32 age = 2;  
  9.     optional int64 phone = 3;  
  10.     repeated serialize.UserVo friends = 4;  
  11. }  

 

2、在命令行利用protoc 工具生成builder类

Shell代码  收藏代码
  1. protoc -IPATH=.proto文件所在的目录 --java_out=java文件的输出路径  .proto的名称   

 得到UserVoProtos类

 

3、编写序列化代码

Java代码  收藏代码
  1. UserVoProtos.UserVo.Builder builder = UserVoProtos.UserVo.newBuilder();  
  2. builder.setName("Yaoming");  
  3. builder.setAge(30);  
  4. builder.setPhone(13789878978L);  
  5.           
  6. UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder();  
  7. builder1.setName("tmac");  
  8. builder1.setAge(32);  
  9. builder1.setPhone(138999898989L);  
  10.           
  11. UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder();  
  12. builder2.setName("liuwei");  
  13. builder2.setAge(29);  
  14. builder2.setPhone(138999899989L);  
  15.           
  16. builder.addFriends(builder1);  
  17. builder.addFriends(builder2);  
  18.           
  19. UserVoProtos.UserVo vo = builder.build();  
  20.           
  21. byte[] v = vo.toByteArray();  

 字节数53

 

4、反序列化

Java代码  收藏代码
  1. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  2. System.out.println(uvo.getFriends(0).getName());  
 结果:tmac,反序列化成功

google protobuf 优点:字节数很小,适合网络传输节省io,跨语言 。缺点:需要依赖于工具生成代码。

 

  工作机制

proto文件是对数据的一个描述,包括字段名称,类型,字节中的位置。protoc工具读取proto文件生成对应builder代码的类库。protoc xxxxx  --java_out=xxxxxx 生成java类库。builder类根据自己的算法把数据序列化成字节流,或者把字节流根据反射的原理反序列化成对象。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。

proto文件中的字段类型和java中的对应关系:

详见:https://developers.google.com/protocol-buffers/docs/proto

 .proto Type java Type c++ Type
double double double
float float float
int32 int int32
int64 long  int64
uint32 int uint32
unint64 long uint64
sint32 int int32
sint64 long int64
fixed32 int uint32
fixed64 long uint64
sfixed32 int int32
sfixed64 long int64
bool boolean bool
string String string
bytes byte string
字段属性的描述:
写道
required: a well-formed message must have exactly one of this field.
optional: a well-formed message can have zero or one of this field (but not more than one).
repeated: this field can be repeated any number of times (including zero) in a well-formed message. The order of the repeated values will be preserved.
 
protobuf 在序列化和反序列化的时候,是依赖于.proto文件生成的builder类完成,字段的变化如果不表现在.proto文件中就不会影响反序列化,比较适合字段变化的情况。做个测试:
把UserVo序列化到文件中:
Java代码  收藏代码
  1. UserVoProtos.UserVo vo = builder.build();  
  2. byte[] v = vo.toByteArray();  
  3. FileOutputStream fos = new FileOutputStream(dataFile);  
  4. fos.write(vo.toByteArray());  
  5. fos.close();  
 
为UserVo增加字段,对应的.proto文件:
Text代码  收藏代码
  1. package serialize;  
  2.   
  3. option java_package = "serialize";  
  4. option java_outer_classname="UserVoProtos";  
  5.   
  6. message UserVo{  
  7.     optional string name = 1;  
  8.     optional int32 age = 2;  
  9.     optional int64 phone = 3;  
  10.     repeated serialize.UserVo friends = 4;  
  11.     optional string address = 5;  
  12. }  
 
从文件中反序列化回来:
Java代码  收藏代码
  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. byte[] dstb = new byte[fis.available()];  
  3. for(int i=0;i<dstb.length;i++){  
  4.     dstb[i] = (byte)fis.read();  
  5. }  
  6. fis.close();  
  7. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  8. System.out.println(uvo.getFriends(0).getName());  
 成功得到结果。
三种方式对比传输同样的数据,google protobuf只有53个字节是最少的。

结论

方式优点缺点
JSON

跨语言、格式清晰一目了然

字节数比较大,需要第三方类库
Object 
Serialize
Java原生方法不依赖外部类库字节数比较大,不能跨语言
Google protobuf

跨语言、字节数比较少

编写.proto配置用protoc工具生成对应的代码

 

<think>好的,用户想了解Java中常见的序列化方法以及相关的序列化器。首先,我需要回忆一下Java原生的序列化方式,记得是通过实现Serializable接口,但可能性能和兼容性有问题。然后,用户提供的引用里提到了JSON序列化,比如用Jackson或者Gson库,这些应该属于文本格式的序列化方法。还有提到的Kryo,性能很好,特别是针对Java语言,Dubbo推荐过。另外还有protobuf和protostuff,这些是二进制序列化框架,可能需要定义schema,性能应该更高。 接下来要整理这些信息,确保覆盖主要的序列化方式:原生、JSON、二进制(比如Kryo、protobuf、protostuff)。每个方式要说明特点、优缺点以及适用场景。还要注意用户提供的引用中提到的性能对比,比如Kryo在性能上的优势,protobuf生成代码的方式,以及原生序列化的问题。 需要检查是否漏掉其他常见的序列化器,比如Hessian或者Avro,但用户给的引用里没有提到,可能不需要包含。另外,要确保回答结构清晰,分点列出,每个方式有对应的序列化器举例,比如JSON的Jackson,二进制中的Kryo等。还要注意引用对应的来源,比如引用2提到Kryo和Dubbo的推荐,引用3和4提到protobuf和性能对比。 最后,生成相关问题需要考虑用户可能的后续疑问,比如如何选择适合的序列化方式,性能优化的方法,不同场景的应用,或者具体技术如protobuf的使用步骤。确保问题覆盖用户可能感兴趣的方向,帮助进一步学习。</think>### Java序列化的常见方式序列化器 #### 1. **Java原生序列化** - **特点**:基于`java.io.Serializable`接口实现,通过`ObjectOutputStream`和`ObjectInputStream`实现对象的序列化与反序列化。 - **优点**:简单易用,无需额外依赖。 - **缺点**:生成的字节码体积大、性能较低,且跨语言兼容性差[^1][^3]。 - **示例代码**: ```java public class User implements Serializable { private String name; private int age; } ``` #### 2. **JSON序列化** - **特点**:将对象转换为JSON格式的字符串,可读性强,支持跨语言。 - **常用序列化器**: - **Jackson**:性能高,功能丰富,支持注解。 - **Gson**:Google开发,使用简单。 - **Fastjson**:阿里巴巴开源,解析速度快(但需注意安全漏洞)。 - **适用场景**:RESTful API、前后端数据交互。 #### 3. **二进制序列化** - **Kryo** - **特点**:专为Java设计,序列化速度快,字节体积小。 - **适用场景**:高性能要求的场景,如分布式系统(Dubbo推荐使用Kryo作为序列化方式)[^2]。 - **示例**: ```java Kryo kryo = new Kryo(); Output output = new Output(new FileOutputStream("file.bin")); kryo.writeObject(output, user); output.close(); ``` - **Protocol Buffers(protobuf)** - **特点**:需预定义`.proto`文件,生成的代码支持序列化/反序列化,性能高且跨语言。 - **示例**: ```protobuf message User { required string name = 1; required int32 age = 2; } ``` - **优点**:数据压缩率高,适合网络传输[^3][^4]。 - **Protostuff** - **特点**:无需预定义Schema,基于POJO直接序列化,性能接近protobuf。 - **适用场景**:需要灵活性和高性能的场景[^3]。 #### 4. **其他序列化方式** - **Hessian**:支持跨语言,二进制协议,但性能弱于Kryo和protobuf。 - **Avro**:基于Schema,适合大数据处理(如Hadoop生态)。 --- ### 性能对比(引用数据) - **序列化速度**:Kryo > Protostuff > protobuf > JSON > Java原生序列化[^3]。 - **字节大小**:protobuf ≈ Kryo < JSON < Java原生序列化[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值