0、前言
本文主要对几种常见Java序列化方式进行实现。包括Java原生以流的方法进行的序列化、Json序列化、FastJson序列化、Protobuff序列化。
1、Java原生序列化
Java原生序列化方法即通过Java原生流(InputStream和OutputStream之间的转化)的方式进行转化。需要注意的是JavaBean实体类必须实现Serializable接口,否则无法序列化。Java原生序列化代码示例如下所示:
1 package serialize; 2 3 import java.io.BufferedInputStream; 4 import java.io.ByteArrayOutputStream; 5 import java.io.IOException; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutputStream; 8 import java.util.ArrayList; 9 import java.util.List; 10 /** 11 * 12 * @author liqqc 13 * 14 */ 15 public class JavaSerialize { 16 public static void main(String[] args) throws ClassNotFoundException, IOException { 17 new JavaSerialize().start(); 18 } 19 20 public void start() throws IOException, ClassNotFoundException { 21 User u = new User(); 22 List<User> friends = new ArrayList<>(); 23 u.setUserName("张三"); 24 u.setPassWord("123456"); 25 u.setUserInfo("张三是一个很牛逼的人"); 26 u.setFriends(friends); 27 28 User f1 = new User(); 29 f1.setUserName("李四"); 30 f1.setPassWord("123456"); 31 f1.setUserInfo("李四是一个很牛逼的人"); 32 33 User f2 = new User(); 34 f2.setUserName("王五"); 35 f2.setPassWord("123456"); 36 f2.setUserInfo("王五是一个很牛逼的人"); 37 38 friends.add(f1); 39 friends.add(f2); 40 41 Long t1 = System.currentTimeMillis(); 42 ByteArrayOutputStream out = new ByteArrayOutputStream(); 43 ObjectOutputStream obj = new ObjectOutputStream(out); 44 for(int i = 0; i<10; i++) { 45 obj.writeObject(u); 46 } 47 System.out.println("java serialize: " +(System.currentTimeMillis() - t1) + "ms; 总大小:" + out.toByteArray().length ); 48 49 Long t2 = System.currentTimeMillis(); 50 ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new java.io.ByteArrayInputStream(out.toByteArray()))); 51 User user = (User) ois.readObject(); 52 System.out.println("java deserialize: " + (System.currentTimeMillis() - t2) + "ms; User: " + user); 53 } 54 55 }
运行结果:
java serialize: 8ms; 总大小:420 java deserialize: 1ms; User: User [userId=null, userName=张三, passWord=123456, userInfo=张三是一个很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一个很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一个很牛逼的人, friends=null]]]
2、Json序列化
Json序列化一般会使用jackson包,通过ObjectMapper类来进行一些操作,比如将对象转化为byte数组或者将json串转化为对象。现在的大多数公司都将json作为服务器端返回的数据格式。比如调用一个服务器接口,通常的请求为xxx.json?a=xxx&b=xxx的形式。Json序列化示例代码如下所示:
1 package serialize; 2 3 import java.io.IOException; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import com.fasterxml.jackson.databind.ObjectMapper; 8 /** 9 * 10 * @author liqqc 11 * 12 */ 13 public class JsonSerialize { 14 public static void main(String[] args) throws IOException { 15 new JsonSerialize().start(); 16 } 17 18 public void start() throws IOException { 19 User u = new User(); 20 List<User> friends = new ArrayList<>(); 21 u.setUserName("张三"); 22 u.setPassWord("123456"); 23 u.setUserInfo("张三是一个很牛逼的人"); 24 u.setFriends(friends); 25 26 User f1 = new User(); 27 f1.setUserName("李四"); 28 f1.setPassWord("123456"); 29 f1.setUserInfo("李四是一个很牛逼的人"); 30 31 User f2 = new User(); 32 f2.setUserName("王五"); 33 f2.setPassWord("123456"); 34 f2.setUserInfo("王五是一个很牛逼的人"); 35 36 friends.add(f1); 37 friends.add(f2); 38 39 ObjectMapper mapper = new ObjectMapper(); 40 Long t1 = System.currentTimeMillis(); 41 byte[] writeValueAsBytes = null; 42 for (int i = 0; i < 10; i++) { 43 writeValueAsBytes = mapper.writeValueAsBytes(u); 44 } 45 System.out.println("json serialize: " + (System.currentTimeMillis() - t1) + "ms; 总大小:" + writeValueAsBytes.length); 46 Long t2 = System.currentTimeMillis(); 47 User user = mapper.readValue(writeValueAsBytes, User.class); 48 System.out.println("json deserialize: " + (System.currentTimeMillis() - t2) + "ms; User: " + user); 49 50 } 51 }
运行结果:
json serialize: 55ms; 总大小:341 json deserialize: 35ms; User: User [userId=null, userName=张三, passWord=123456, userInfo=张三是一个很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一个很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一个很牛逼的人, friends=null]]]
3、FastJson序列化
fastjson 是由阿里巴巴开发的一个性能很好的Java 语言实现的 Json解析器和生成器。特点:速度快,测试表明fastjson具有极快的性能,超越任其他的java json parser。功能强大,完全支持java bean、集合、Map、日期、Enum,支持范型和自省。无依赖,能够直接运行在Java SE 5.0以上版本
支持Android。使用时候需引入FastJson第三方jar包。FastJson序列化代码示例如下所示:
1 package serialize; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import com.alibaba.fastjson.JSON; 7 /** 8 * 9 * @author liqqc 10 * 11 */ 12 public class FastJsonSerialize { 13 14 public static void main(String[] args) { 15 new FastJsonSerialize().start(); 16 } 17 18 public void start(){ 19 User u = new User(); 20 List<User> friends = new ArrayList<>(); 21 u.setUserName("张三"); 22 u.setPassWord("123456"); 23 u.setUserInfo("张三是一个很牛逼的人"); 24 u.setFriends(friends); 25 26 User f1 = new User(); 27 f1.setUserName("李四"); 28 f1.setPassWord("123456"); 29 f1.setUserInfo("李四是一个很牛逼的人"); 30 31 User f2 = new User(); 32 f2.setUserName("王五"); 33 f2.setPassWord("123456"); 34 f2.setUserInfo("王五是一个很牛逼的人"); 35 36 friends.add(f1); 37 friends.add(f2); 38 39 //序列化 40 Long t1 = System.currentTimeMillis(); 41 String text = null; 42 for(int i = 0; i<10; i++) { 43 text = JSON.toJSONString(u); 44 } 45 System.out.println("fastJson serialize: " +(System.currentTimeMillis() - t1) + "ms; 总大小:" + text.getBytes().length); 46 //反序列化 47 Long t2 = System.currentTimeMillis(); 48 User user = JSON.parseObject(text, User.class); 49 System.out.println("fastJson serialize: " + (System.currentTimeMillis() -t2) + "ms; User: " + user); 50 } 51 }
运行结果:
fastJson serialize: 284ms; 总大小:269 fastJson serialize: 26ms; User: User [userId=null, userName=张三, passWord=123456, userInfo=张三是一个很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一个很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一个很牛逼的人, friends=null]]]
4、ProtoBuff序列化
ProtocolBuffer是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化。适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
优点:跨语言;序列化后数据占用空间比JSON小,JSON有一定的格式,在数据量上还有可以压缩的空间。
缺点:它以二进制的方式存储,无法直接读取编辑,除非你有 .proto 定义,否则无法直接读出 Protobuffer的任何内容。
其与thrift的对比:两者语法类似,都支持版本向后兼容和向前兼容,thrift侧重点是构建跨语言的可伸缩的服务,支持的语言多,同时提供了全套RPC解决方案,可以很方便的直接构建服务,不需要做太多其他的工作。 Protobuffer主要是一种序列化机制,在数据序列化上进行性能比较,Protobuffer相对较好。
ProtoBuff序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。对于大量数据的缓存,也可以提高缓存中数据存储量。原始的ProtoBuff需要自己写.proto文件,通过编译器将其转换为java文件,显得比较繁琐。百度研发的jprotobuf框架将Google原始的protobuf进行了封装,对其进行简化,仅提供序列化和反序列化方法。其实用上也比较简洁,通过对JavaBean中的字段进行注解就行,不需要撰写.proto文件和实用编译器将其生成.java文件,百度的jprotobuf都替我们做了这些事情了。
一个带有jprotobuf注解的JavaBean如下所示,如果你想深入学习可以参照https://github.com/google/protobuf。
1 package serialize; 2 3 import java.io.Serializable; 4 import java.util.List; 5 import com.baidu.bjf.remoting.protobuf.FieldType; 6 import com.baidu.bjf.remoting.protobuf.annotation.Protobuf; 7 8 public class User implements Serializable { 9 private static final long serialVersionUID = -7890663945232864573L; 10 11 @Protobuf(fieldType = FieldType.INT32, required = false, order = 1) 12 private Integer userId; 13 14 @Protobuf(fieldType = FieldType.STRING, required = false, order = 2) 15 private String userName; 16 17 @Protobuf(fieldType = FieldType.STRING, required = false, order = 3) 18 private String passWord; 19 20 @Protobuf(fieldType = FieldType.STRING, required = false, order = 4) 21 private String userInfo; 22 23 @Protobuf(fieldType = FieldType.OBJECT, required = false, order = 5) 24 private List<User> friends; 25 26 public Integer getUserId() { 27 return userId; 28 } 29 30 public void setUserId(Integer userId) { 31 this.userId = userId; 32 } 33 34 public String getUserName() { 35 return userName; 36 } 37 38 public void setUserName(String userName) { 39 this.userName = userName; 40 } 41 42 public String getPassWord() { 43 return passWord; 44 } 45 46 public void setPassWord(String passWord) { 47 this.passWord = passWord; 48 } 49 50 public String getUserInfo() { 51 return userInfo; 52 } 53 54 public void setUserInfo(String userInfo) { 55 this.userInfo = userInfo; 56 } 57 58 public List<User> getFriends() { 59 return friends; 60 } 61 62 public void setFriends(List<User> friends) { 63 this.friends = friends; 64 } 65 66 @Override 67 public String toString() { 68 return "User [userId=" + userId + ", userName=" + userName + ", passWord=" + passWord + ", userInfo=" + userInfo 69 + ", friends=" + friends + "]"; 70 } 71 72 }
jprotobuf序列化代码示例如下所示:
1 package serialize; 2 3 import java.io.IOException; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import com.baidu.bjf.remoting.protobuf.Codec; 8 import com.baidu.bjf.remoting.protobuf.ProtobufProxy; 9 /** 10 * 11 * @author liqqc 12 * 13 */ 14 public class ProtoBuffSerialize { 15 16 public static void main(String[] args) throws IOException { 17 new ProtoBuffSerialize().start(); 18 } 19 20 public void start() throws IOException { 21 Codec<User> studentClassCodec = ProtobufProxy.create(User.class, false); 22 23 User u2 = new User(); 24 List<User> friends = new ArrayList<>(); 25 u2.setUserName("张三"); 26 u2.setPassWord("123456"); 27 u2.setUserInfo("张三是一个很牛逼的人"); 28 u2.setFriends(friends); 29 30 User f1 = new User(); 31 f1.setUserName("李四"); 32 f1.setPassWord("123456"); 33 f1.setUserInfo("李四是一个很牛逼的人"); 34 35 User f2 = new User(); 36 f2.setUserName("王五"); 37 f2.setPassWord("123456"); 38 f2.setUserInfo("王五是一个很牛逼的人"); 39 friends.add(f1); 40 friends.add(f2); 41 42 Long stime_jpb_encode = System.currentTimeMillis(); 43 byte[] bytes = null; 44 for(int i = 0; i<10; i++) { 45 bytes = studentClassCodec.encode(u2); 46 } 47 System.out.println("jprotobuf序列化耗时:" + (System.currentTimeMillis() - stime_jpb_encode) + "ms; 总大小:" + bytes.length); 48 49 Long stime_jpb_decode = System.currentTimeMillis(); 50 User user = studentClassCodec.decode(bytes); 51 Long etime_jpb_decode = System.currentTimeMillis(); 52 System.out.println("jprotobuf反序列化耗时:"+ (etime_jpb_decode-stime_jpb_decode) + "ms; User: " + user); 53 } 54 55 }
运行结果:
jprotobuf序列化耗时:9ms; 总大小:148 jprotobuf反序列化耗时:0ms; User: User [userId=null, userName=张三, passWord=123456, userInfo=张三是一个很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一个很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一个很牛逼的人, friends=null]]]
5、总结
我们通过Main方法来进行对比测试,(但是通过测试发现少量数据无法准确显示每种序列化方式的优劣,故这里无法给出比较好的答案,仅供参考)。示例代码如下所示:
1 package serialize; 2 3 import java.io.IOException; 4 5 /** 6 * @author liqqc 7 */ 8 public class Main { 9 10 public static void main(String[] args) throws IOException, ClassNotFoundException { 11 12 ProtoBuffSerialize protoBuffSerialize = new ProtoBuffSerialize(); 13 protoBuffSerialize.start(); 14 15 System.err.println(); 16 System.err.println(); 17 18 JavaSerialize javaSerialize = new JavaSerialize(); 19 javaSerialize.start(); 20 System.err.println(); 21 22 JsonSerialize jsonSerialize = new JsonSerialize(); 23 jsonSerialize.start(); 24 System.err.println(); 25 26 FastJsonSerialize fastJsonSerialize = new FastJsonSerialize(); 27 fastJsonSerialize.start(); 28 } 29 }
运行结果:
jprotobuf序列化耗时:7ms; 总大小:148 jprotobuf反序列化耗时:0ms java serialize: 6ms; 总大小:420 java deserialize: 1ms json serialize: 37ms; 总大小:341 json deserialize: 27ms fastJson serialize: 173ms; 总大小:269 fastJson serialize: 35ms
上面的测试仅供参考,并不能代表通过大量数据进行测试的结果。可以发现:序列化后对象的所占大小上:protobuff序列化所占总大小是最少的;其次是fastJson序列化;最后是json序列化和java原生序列化。对于序列化耗时,上面的测试不准。
还是去看看专业测试分析吧,具体情况可以进去看看https://github.com/eishay/jvm-serializers/wiki
本文仅仅简单介绍了下几种序列化方式的实现,并未经过大量测试对其进行对比分析,待后续有时间和精力在进行补充