概念
序列化:将Java对象转化为字节数组
反序列化:将字节数组转化为Java对象
在RPC应用中,进行跨进程远程调用的时候,需要使用特定的序列化技术,需要对进行网络传输的对象进行序列化和反序列化。
影响序列化选择有两个因素
1. 序列化之后码流的大小,如果太大,那么将会影响网络传输的性能。
2. 序列化和反序列化过程的性能
常用的序列化框架性能比较
本文主要进行以下序列化框架的对比测试:
JDK
FastJson
Hessian
Protostuff
准备
需要序列化的对象,这是一个复杂的对象。
NettyMessage
public class NettyMessage implementsSerializable {//消息头
privateHeader header;//消息体
privateObject body;
}
@Datapublic class Header implementsSerializable {//校验头
private intcrcCode;//消息头消息体的总长度
private intlength;//全局唯一id
private longsessionId;//消息类型
privateMessageType type;//扩展字段
private Mapattachment;
}
@Datapublic class RpcRequest implementsSerializable {private long requestId; //请求id
private String interfaceName; //调用类名
private String methodName; //调用方法名
private String[] parameterTypes; //方法参数类型
private Object[] parameters; //方法参数
}
创建一个构造器创建该对象。
public classNettyMessageBuilder {public staticNettyMessage build(){
NettyMessage message= newNettyMessage();
Header header= newHeader();
RpcRequest request= newRpcRequest();
header.setCrcCode(1234);
header.setType(MessageType.APP_RESPONE_TYPE);
header.setLength(100);
header.setSessionId(200);
Map map = new LinkedHashMap<>();
map.put("demoKey",(Object)"demoValue");
header.setAttachment(map);
request.setInterfaceName("com.demo");
String[] types= {"java.lang.String" ,"java.lang.Integer"};
String[] param= {"java.lang.String" ,"java.lang.Integer"};
request.setParameterTypes(types);
request.setParameters(param);
request.setMethodName("buy");
request.setRequestId(123456);
message.setHeader(header);
message.setBody(request);returnmessage;
}
}
定义序列化接口
public abstract classAbstractSerialize {public abstract byte[] serialize(T obj);public abstract T deserialize(byte[] data, Classclazz);
}
JDK
实现
public class JdkSerializeUtil extendsAbstractSerialize {public byte[] serialize(T obj) {if (obj == null){throw newNullPointerException();
}
ByteArrayOutputStream bos= newByteArrayOutputStream();try{
ObjectOutputStream oos= newObjectOutputStream(bos);
oos.writeObject(obj);returnbos.toByteArray();
}catch(Exception ex) {
ex.printStackTrace();
}return new byte[0];
}public T deserialize(byte[] data, Classclazz) {
ByteArrayInputStream bis= newByteArrayInputStream(data);try{
ObjectInputStream ois= newObjectInputStream(bis);
T obj=(T)ois.readObject();returnobj;
}catch(Exception ex) {
ex.printStackTrace();
}return null;
}
}
FastJson
引入pom
com.alibaba
fastjson
1.2.56
实现
public class FastjsonSerializeUtil extendsAbstractSerialize {public byte[] serialize(T obj) {if (obj == null){throw newNullPointerException();
}
String json=JSON.toJSONString(obj);byte[] data =json.getBytes();returndata;
}public T deserialize(byte[] data, Classclazz) {
T obj= JSON.parseObject(newString(data),clazz);returnobj;
}
}
Hessian
com.caucho
hessian
4.0.60
实现
@Slf4jpublic class HessianSerializeUtil extendsAbstractSerialize {public byte[] serialize(T obj) {if (obj == null){throw newNullPointerException();
}try{
ByteArrayOutputStream bos= newByteArrayOutputStream();
HessianOutput ho= newHessianOutput(bos);
ho.writeObject(obj);returnbos.toByteArray();
}catch(Exception ex){
log.error("HessianSerializeUtil序列化发生异常!"+ex);throw newRuntimeException();
}
}public T deserialize(byte[] data, Classclazz) {if (data == null){throw newNullPointerException();
}try{
ByteArrayInputStream bis= newByteArrayInputStream(data);
HessianInput hi= newHessianInput(bis);return(T)hi.readObject();
}catch(Exception ex){
log.error("HessianSerializeUtil反序列化发生异常!"+ex);throw newRuntimeException();
}
}
}
Protostuff
io.protostuff
protostuff-core
1.6.0
compile
io.protostuff
protostuff-runtime
1.6.0
实现
public class ProtostuffSerializeUtil extendsAbstractSerialize {/*** 避免每次序列化都重新申请Buffer空间*/
private static LinkedBuffer buffer =LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);/*** 缓存Schema*/
private static Map, Schema>> schemaCache = new ConcurrentHashMap, Schema>>();public byte[] serialize(T obj) {if (obj == null){throw newNullPointerException();
}
Class clazz = (Class) obj.getClass();
Schema schema =getSchema(clazz);byte[] data;try{
data=ProtostuffIOUtil.toByteArray(obj, schema, buffer);
}finally{
buffer.clear();
}returndata;
}public T deserialize(byte[] data, Classclazz) {
Schema schema =getSchema(clazz);
T obj=schema.newMessage();
ProtostuffIOUtil.mergeFrom(data, obj, schema);returnobj;
}private static Schema getSchema(Classclazz) {
Schema schema = (Schema) schemaCache.get(clazz);if (schema == null) {//这个schema通过RuntimeSchema进行懒创建并缓存//所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的
schema =RuntimeSchema.getSchema(clazz);if (schema != null) {
schemaCache.put(clazz, schema);
}
}returnschema;
}
}
测试
测试方法
@Testpublic voidtestFastJsonSerialize(){
//这里替换各种序列化实现类
AbstractSerialize serialize= newProtostuffSerializeUtil();
NettyMessage message=NettyMessageBuilder.build();
TimeUtil timeUtil= newTimeUtil();
TimeUtil timeUtil1= newTimeUtil();
NettyMessage result= null;byte[] serByte =serialize.serialize(message);
System.out.println("字节长度:" +serByte.length);
result= serialize.deserialize(serByte,NettyMessage.class);
//这里设置测试次数for(int i = 0; i< 100000; i++){//timeUtil.init();
timeUtil.start();
serByte=serialize.serialize(message);
timeUtil.end();//System.out.println("序列化时间:"+ timeUtil.getAvrTimeUs() + " Us");
timeUtil1.start();
result= serialize.deserialize(serByte,NettyMessage.class);
timeUtil1.end();
}
System.out.println("序列化时间:"+ timeUtil.getAvrTimeUs() + " Us");
System.out.println("反序列化时间:"+ timeUtil1.getAvrTimeUs() + " Us");
System.out.println("结果:" +result);
}
这里定义了一个TimeUtil类来计时
public classTimeUtil {private longstartTime;private longendTime;private longtimeSum;private longcount;public voidinit(){
timeSum= 0;
count= 0;
}public voidstart(){
startTime=System.nanoTime();
}public voidend(){
endTime=System.nanoTime();
timeSum+= (endTime-startTime);
count++;
}public longgetAvrTimeNs(){return (timeSum/count);
}public longgetAvrTimeUs(){return (timeSum/count)/1000;
}public longgetAvrTimeMs(){return (timeSum/count)/1000000;
}
}
码流大小(byte)
10次(us)
100次(us)
1000次(us)
10000次(us)
100000次(us)
FastJson
305
116-243
106-185
90-140
26-39
8-12
JDK
866
383-777
502-1101
123-334
54-237
15-76
Hessian
520
959-3836
376-567
191-329
99-161
30-47
Protostuff
193
103-145
90-137
75-135
15-24
5-8
注:
1. 码流单位为字节
2. 序列化耗时-反序列化耗时,单位为微秒
从以上测试可以看出
1. JDK方式的码流最大,不利于网络传输。
2. 从整体来看,Prorostuff的码流最小,序列化性能最好。