序列化:將Java對象轉化爲字節數組java
反序列化:將字節數組轉化爲Java對象json
在RPC應用中,進行跨進程遠程調用的時候,須要使用特定的序列化技術,須要對進行網絡傳輸的對象進行序列化和反序列化。數組
影響序列化選擇有兩個因素緩存
1. 序列化以後碼流的大小,若是太大,那麼將會影響網絡傳輸的性能。安全
2. 序列化和反序列化過程的性能網絡
經常使用的序列化框架性能比較session
本文主要進行如下序列化框架的對比測試:框架
須要序列化的對象,這是一個複雜的對象。性能
NettyMessage
public class NettyMessage implements Serializable { //消息頭 private Header header; //消息體 private Object body; } @Data public class Header implements Serializable { //校驗頭 private int crcCode; //消息頭消息體的總長度 private int length; //全局惟一id private long sessionId; //消息類型 private MessageType type; //擴展字段 private Map<String,Object> attachment; } @Data public class RpcRequest implements Serializable { private long requestId; //請求id private String interfaceName; //調用類名 private String methodName; //調用方法名 private String[] parameterTypes; //方法參數類型 private Object[] parameters; //方法參數 }
建立一個構造器建立該對象。測試
public class NettyMessageBuilder { public static NettyMessage build(){ NettyMessage message = new NettyMessage(); Header header = new Header(); RpcRequest request = new RpcRequest(); header.setCrcCode(1234); header.setType(MessageType.APP_RESPONE_TYPE); header.setLength(100); header.setSessionId(200); Map<String,Object> 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); return message; } }
定義序列化接口
public abstract class AbstractSerialize { public abstract <T> byte[] serialize(T obj); public abstract <T> T deserialize(byte[] data, Class<T> clazz); }
實現
public class JdkSerializeUtil extends AbstractSerialize { public <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]; } public <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; } }
引入pom
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.56</version> </dependency>
實現
public class FastjsonSerializeUtil extends AbstractSerialize { public <T> byte[] serialize(T obj) { if (obj == null){ throw new NullPointerException(); } String json = JSON.toJSONString(obj); byte[] data = json.getBytes(); return data; } public <T> T deserialize(byte[] data, Class<T> clazz) { T obj = JSON.parseObject(new String(data),clazz); return obj; } }
<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.60</version> </dependency>
實現
@Slf4j public class HessianSerializeUtil extends AbstractSerialize { public <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){ log.error("HessianSerializeUtil序列化發生異常!"+ex); throw new RuntimeException(); } } public <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){ log.error("HessianSerializeUtil反序列化發生異常!"+ex); throw new RuntimeException(); } } }
<dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.6.0</version> <scope>compile</scope> </dependency> <!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-runtime --> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.6.0</version> </dependency>
實現
public class ProtostuffSerializeUtil extends AbstractSerialize { /** * 避免每次序列化都從新申請Buffer空間 */ private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); /** * 緩存Schema */ private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<Class<?>, Schema<?>>(); public <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; } public <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; } }
測試方法
@Test public void testFastJsonSerialize(){
//這裏替換各類序列化實現類 AbstractSerialize serialize = new ProtostuffSerializeUtil(); NettyMessage message = NettyMessageBuilder.build(); TimeUtil timeUtil = new TimeUtil(); TimeUtil timeUtil1 = new TimeUtil(); 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 class TimeUtil { private long startTime; private long endTime; private long timeSum; private long count; public void init(){ timeSum = 0; count = 0; } public void start(){ startTime = System.nanoTime(); } public void end(){ endTime = System.nanoTime(); timeSum += (endTime-startTime); count++; } public long getAvrTimeNs(){ return (timeSum/count); } public long getAvrTimeUs(){ return (timeSum/count)/1000; } public long getAvrTimeMs(){ 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的碼流最小,序列化性能最好。