文章目录
- 一、简介
- 二、序列化框架
- 1、JDK
- 2、XML序列化
- 3、JSON序列化
- 4、Hessian
- 5、Avro序列化
- 6、Kyro序列化
- 7、Protostuff
- 三、序列化框架对比测试
- 1、对象准备
- 2、JDK方式
- 3、FastJson方式
- 4、Hessian方式
- 5、Protostuff方式
- 6、测试代码
- 四、总结
- 五、序列化应用场景
- 六、注意事项
一、简介
序列化
:将Java对象转化成字节数组。
反序列化
:将字节数组转化成Java对象。
影响序列化选择有两个因素:
-
序列化之后码流的大小,如果太大,那么将会影响网络传输的性能。
-
序列化和反序列化过程的性能。
二、序列化框架
1、JDK
2、XML序列化
XML
协议,良好的可读性,自由度极高的扩展性,成了很长一段时间的序列化标准规范;
可以说XML序列化是开发中最常见也是发展时间最久的协议,并且支持跨进程和跨语言交互。
但是缺陷也很明显,即XML规范下的每一个属性和值都是固定的标签形式,导致序列化后的字节流文件很大,而且解析复杂,效率很低。
方案:最常见的是 XStream
和 Java自带的XML序列化和反序列化
两种。
3、JSON序列化
XML序列化发展了多年后,也浮现了一些问题,比如开发并不简便,解析XML复杂度较高,还有XML的标准规范比较多,自由度过高,导致很难有效的指定格式校验等,于是一种新的 轻量级的序列化交互的方案--JSON(JavaScript Object Notation)
出现了,相对于XML来说,json格式语法简单,自由度较高,有很高的可读性
,并且在JSON序列化后的字节流小于XML序列化的结果,解析起来更方便,于是基于JSON的接口成了新的标准规范之一。
方案:最常见的Jackson
、阿里巴巴开源的FastJson
、谷歌的GSON
。
4、Hessian
简单说来,Hessian
是一个轻量级的RPC框架。
它基于HTTP协议传输,使用Hessian二进制序列化,对于数据包比较大的情况比较友好。
5、Avro序列化
Avro
序列化设计初衷是为了支持大批量数据交换的应用,支持二进制序列化方式,并且自身提供了动态语言支持,可以更加便捷、快速处理大批量的Avro数据
6、Kyro序列化
Kyro序列化
是主流的比较成熟的序列化方案之一,目前广泛使用在大数据组件中,比如Hive、Storm等,性能比起Hessian还要优越,但是缺陷较明显,不支持跨语言交互,在dubbo2.6.x版本开始已经加入了Kyro序列化的支持。
7、Protostuff
Protobuf
是谷歌提出的序列化方案,不同的是此方案 独立于语言、平台
,谷歌提供了多个语言如java、c、go、python等语言的实现,也提供了多平台的库文件支持,使用比较广泛,优点在于 性能开销很小,压缩率很高
,但是缺陷也很明显,可读性很差,并且protobuf需要使用特定语言的库进行翻译转换,使用起来较为麻烦。
三、序列化框架对比测试
下面我们挑选 JDK
、FastJson
、Hessian
、Protostuff
四种序列化方式进行对比测试。
测试之前我们需要准备序列化对象,对象在序列化时,需要注意以下几个关键字:
-
Serializable
:接口,是一个标志性接口,标识可以在 JVM 中进行序列化,JVM 会为该类自动生成一个序列化版本号。参与序列化与反序列化的类必须实现 Serializable 接口。 -
serialVersionUID
:类属性,序列化版本号,用于给 JVM 区别同名类,没有提供版本号,JVM会默认提供序列化版本号。 -
transient
:关键字,当序列化时,不希望某些属性参与,则可以使用这个关键字标注该属性。
1、对象准备
SHeader.java
package com.springboottest.serialize.model;import java.io.Serializable;
import java.util.Map;public class SHeader implements Serializable {private int code;private int length;private String type;private Map<String, Object> attach;public int getCode() {return code;}public void setCode(int code) {this.code = code;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}public String getType() {return type;}public void setType(String type) {this.type = type;}public Map<String, Object> getAttach() {return attach;}public void setAttach(Map<String, Object> attach) {this.attach = attach;}
}
SRequest.java
package com.springboottest.serialize.model;import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;public class SRequest implements Serializable {private String version;private SHeader header;private Object body;public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public SHeader getHeader() {return header;}public void setHeader(SHeader header) {this.header = header;}public Object getBody() {return body;}public void setBody(Object body) {this.body = body;}public static class SRequestBuilder{public static SRequest build(){SRequest request = new SRequest();request.setVersion("1.0.0");SHeader header = new SHeader();header.setCode(200);header.setLength(202);header.setType("Object");Map<String, Object> attach = new HashMap<String, Object>();attach.put("name", "davis");attach.put("age", 30);header.setAttach(attach);request.setHeader(header);request.setBody("{\"code\":200, \"msg\":\"success\"}");return request;}}
}
序列化接口 AbstractSerialize.java
package com.springboottest.serialize;public abstract class AbstractSerialize {public abstract <T> byte[] serialize(T obj);public abstract <T> T deserialize(byte[] data, Class<T> clazz);
}
2、JDK方式
JDK自带的序列化反序列化方式需要用到的两个类:
-
ObjectOutputStream
:IO类,包含序列化对象的方法,writeObject(); -
ObjectInputStream
:IO类,包含反序列化对象的方法,readObject();
具体代码 JDKSerialize.java
package com.springboottest.serialize;import com.springboottest.serialize.model.SRequest;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class JDKSerialize extends AbstractSerialize{@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}ByteArrayOutputStream bos = new ByteArrayOutputStream();try {ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(obj);return bos.toByteArray();} catch (Exception ex) {ex.printStackTrace();}return new byte[0];}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {ByteArrayInputStream bis = new ByteArrayInputStream(data);try {ObjectInputStream ois = new ObjectInputStream(bis);T obj = (T)ois.readObject();return obj;} catch (Exception ex) {ex.printStackTrace();}return null;}
}
3、FastJson方式
FastJson 引入方式(pom.xml):
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version>
</dependency>
具体代码 FastjsonSerialize.java
package com.springboottest.serialize;
import com.alibaba.fastjson.JSON;public class FastjsonSerialize extends AbstractSerialize{@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}String json = JSON.toJSONString(obj);byte[] data = json.getBytes();return data;}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {T obj = JSON.parseObject(new String(data),clazz);return obj;}
}
4、Hessian方式
Hessian 引入方式(pom.xml):
<dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>4.0.60</version>
</dependency>
具体代码 HessianSerialize.java
package com.springboottest.serialize;import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;public class HessianSerialize extends AbstractSerialize{@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}try{ByteArrayOutputStream bos = new ByteArrayOutputStream();HessianOutput ho = new HessianOutput(bos);ho.writeObject(obj);return bos.toByteArray();} catch(Exception ex){}return new byte[0];}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {if (data == null){throw new NullPointerException();}try{ByteArrayInputStream bis = new ByteArrayInputStream(data);HessianInput hi = new HessianInput(bis);return (T)hi.readObject();} catch(Exception ex){ex.printStackTrace();}return null;}
}
5、Protostuff方式
Protostuff 引入方式(pom.xml):
<dependency><groupId>io.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.6.0</version><scope>compile</scope>
</dependency><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.6.0</version>
</dependency>
具体代码 ProtostuffSerialize.java
package com.springboottest.serialize;import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class ProtostuffSerialize extends AbstractSerialize{/*** 避免每次序列化都重新申请Buffer空间*/private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);/*** 缓存Schema*/private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<Class<?>, Schema<?>>();@Overridepublic <T> byte[] serialize(T obj) {if (obj == null){throw new NullPointerException();}Class<T> clazz = (Class<T>) obj.getClass();Schema<T> schema = getSchema(clazz);byte[] data;try {data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);} finally {buffer.clear();}return data;}@Overridepublic <T> T deserialize(byte[] data, Class<T> clazz) {Schema<T> schema = getSchema(clazz);T obj = schema.newMessage();ProtostuffIOUtil.mergeFrom(data, obj, schema);return obj;}private static <T> Schema<T> getSchema(Class<T> clazz) {Schema<T> schema = (Schema<T>) schemaCache.get(clazz);if (schema == null) {//这个schema通过RuntimeSchema进行懒创建并缓存//所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的schema = RuntimeSchema.getSchema(clazz);if (schema != null) {schemaCache.put(clazz, schema);}}return schema;}
}
6、测试代码
SerializeTest.java
package com.springboottest.serialize;import com.springboottest.serialize.model.SRequest;public class SerializeTest {public static void main(String[] args) {System.out.println("------------ JDK Serialize ------------");test(new JDKSerialize());System.out.println();System.out.println("------------ FastJson Serialize ------------");test(new FastjsonSerialize());System.out.println();System.out.println("------------ Hessian Serialize ------------");test(new HessianSerialize());System.out.println();System.out.println("------------ Protostuff Serialize ------------");test(new ProtostuffSerialize());System.out.println();}public static void test(AbstractSerialize abstractSerialize){for(int i=0;i<5;i++){testSerialize(abstractSerialize);System.out.println("----------------------");}}public static void testSerialize(AbstractSerialize abstractSerialize){AbstractSerialize serialize = abstractSerialize;SRequest request = SRequest.SRequestBuilder.build();SRequest result = null;byte[] bytes = serialize.serialize(request);System.out.println("字节长度:" + bytes.length);int count = 100;long seriDuration = 0, deseriDuration = 0;long start = 0, end = 0;for(int i=0;i<count;i++){start = System.nanoTime();bytes = serialize.serialize(request);end = System.nanoTime();seriDuration += (end - start);start = System.nanoTime();result = serialize.deserialize(bytes, SRequest.class);end = System.nanoTime();deseriDuration += (end - start);}System.out.println("总次数:" + count);System.out.println("序列化总耗时:" + seriDuration/1000 + "us");System.out.println("反序列化总耗时:" + deseriDuration/1000 + "us");System.out.println("总耗时:" + (seriDuration + deseriDuration)/1000 + "us");}
}
输出结果(序列化和反序列化100次):
------------ JDK Serialize ------------
字节长度:526
总次数:100
序列化总耗时:10616us
反序列化总耗时:73580us
总耗时:84197us
----------------------
字节长度:526
总次数:100
序列化总耗时:5269us
反序列化总耗时:18589us
总耗时:23859us
----------------------
字节长度:526
总次数:100
序列化总耗时:3492us
反序列化总耗时:12892us
总耗时:16385us
----------------------
字节长度:526
总次数:100
序列化总耗时:3483us
反序列化总耗时:9978us
总耗时:13462us
----------------------
字节长度:526
总次数:100
序列化总耗时:2414us
反序列化总耗时:9121us
总耗时:11535us
---------------------------------- FastJson Serialize ------------
字节长度:150
总次数:100
序列化总耗时:7581us
反序列化总耗时:42988us
总耗时:50570us
----------------------
字节长度:150
总次数:100
序列化总耗时:8240us
反序列化总耗时:10351us
总耗时:18592us
----------------------
字节长度:150
总次数:100
序列化总耗时:7231us
反序列化总耗时:9794us
总耗时:17026us
----------------------
字节长度:150
总次数:100
序列化总耗时:5001us
反序列化总耗时:6936us
总耗时:11938us
----------------------
字节长度:150
总次数:100
序列化总耗时:3552us
反序列化总耗时:5458us
总耗时:9011us
---------------------------------- Hessian Serialize ------------
字节长度:243
总次数:100
序列化总耗时:14385us
反序列化总耗时:30783us
总耗时:45168us
----------------------
字节长度:243
总次数:100
序列化总耗时:9114us
反序列化总耗时:13857us
总耗时:22972us
----------------------
字节长度:243
总次数:100
序列化总耗时:7527us
反序列化总耗时:12365us
总耗时:19892us
----------------------
字节长度:243
总次数:100
序列化总耗时:5211us
反序列化总耗时:9830us
总耗时:15042us
----------------------
字节长度:243
总次数:100
序列化总耗时:6858us
反序列化总耗时:10122us
总耗时:16981us
---------------------------------- Protostuff Serialize ------------
字节长度:86
总次数:100
序列化总耗时:2404us
反序列化总耗时:5213us
总耗时:7617us
----------------------
字节长度:86
总次数:100
序列化总耗时:1450us
反序列化总耗时:1668us
总耗时:3119us
----------------------
字节长度:86
总次数:100
序列化总耗时:1183us
反序列化总耗时:1440us
总耗时:2624us
----------------------
字节长度:86
总次数:100
序列化总耗时:1038us
反序列化总耗时:1316us
总耗时:2355us
----------------------
字节长度:86
总次数:100
序列化总耗时:991us
反序列化总耗时:1509us
总耗时:2501us
----------------------
四、总结
序列化方式 | 字节流大小(B) | 100次耗时(us) | 1000次耗时(us) | 10000次耗时(us) |
---|---|---|---|---|
JDK | 526 | 11 ~ 23 | 65 ~ 78 | 215 ~ 385 |
FastJson | 150 | 9 ~ 18 | 11 ~ 46 | 20 ~ 40 |
Hessian | 243 | 15 ~ 23 | 45 ~ 63 | 241 ~ 355 |
Protostuff | 86 | 2 ~ 7 | 4 ~ 10 | 11 ~ 20 |
注:
- 字节流大小单位:B(字节)
- 序列化和反序列化总耗时单位:us(微秒)
从图表中可以看出:
-
JDK方式的码流最大,不利于网络传输
; -
从整体来看,Prorostuff的码流最小,序列化性能最好
。
五、序列化应用场景
-
序列化会将内存中对象的状态转换成二进制文件保存到磁盘当中,当再次使用时会从磁盘中读取该二进制文件,将 Java 对象的状态恢复到内存中。
-
当你想把内存中的对象保存到磁盘文件或数据库中时可以使用序列化。
-
当你想在网络传输中传送 Java 对象时,可以使用序列化。
-
当你想通过 RMI 传输对象时,可以使用序列化。
六、注意事项
-
序列化只会保存对象的属性状态,不会保存对象中的方法。
-
父类实现了
Serializable接口
,则其子类也自动实例化了该接口,也就是说子类不用显式实现Serializable
接口也能参与序列化和反序列化。 -
一个
对象 A
的实例变量引用了其他对象 B
,在 A 对象实例化的过程中 ,也会序列化 B ,前提是 A、B 两个类都实现了Serializable
接口。 -
当一个类实现
Serializable
接口时,最好手动指定一个序列化版本号(serialVersionUID),避免修改源代码后导致反序列化出现异常。 -
当一个类对象会被多次重复使用,且一般不会对其属性做修改,就可以对其进行序列化。例如数据库操作中的实体类。