前言:說到JSON可能大家很熟悉,是目前應用最廣泛的一種序列化格式,它使用起來簡單方便,而且擁有超高的可讀性。但是在越來越多的應用場景里,JSON冗長的缺點導致它並不是一種最優的選擇。
一、常用序列化格式介紹
目前JAVA常用的序列化有protobuf,json,xml,Serializable,hessian,kryo。他們的優缺點如下:
-
JSON:不多說了,用途廣泛,序列化方式還衍生了阿里的fastjson,美團的MSON,谷歌的GSON等更加優秀的轉碼工具。
優點:使用方便。
缺點:數據冗長,轉碼性能一般。 -
XML:很久之前的轉碼方法了,現在用的不多。
優點:暫時沒發現。
缺點:數據冗長,轉碼性能一般。 -
Serialzable:JDK自帶的序列化。
優點:使用方便。
缺點:轉碼性能低下。 -
hessian:基於 binary-RPC實現的遠程通訊library,使用二進制傳輸數據。
優點:數據長度小。
缺點:性能低下。
說了這么多,全是性能低下,MMP一群智障兒?當然不是!kryo就是一款快速、高效的序列化框架,但是它不是我們今天的主角,因為他只能在java中使用,和前端非java語言的通訊就存在極大的隔閡。我們今天的主角是protobuf?emmm,算是吧,但是也不全是,先給大家說下protobuf吧。
- protobuf:谷歌公司出的一款開源項目,性能好,效率高,並且支持多種語言,例如:java,C++,python等。
優點:轉碼性能高,支持多語言。
缺點:中文文檔少,使用相對復雜。
二、protobuf詳解
在使用protobuf之前,需要安裝protobuf編譯器和運行時環境。
由於protobuf是跨平台,跨語言的,所以需要下載和安裝對應版本的編譯器和運行時依賴。
2.1 proto語法介紹
| .proto Type | 說明 | C++ Type | Java Type | Python Type[2] | Go Type |
|---|---|---|---|---|---|
| double | double | double | float | float64 | |
| float | float | float | float | float32 | |
| int32 | 使用可變長度編碼。對負數進行編碼時比較低效 – 如果你的字段要使用負數值,請使用sint32來代替。 | int32 | int | int | int |
| int64 | 使用可變長度編碼。對負數進行編碼時比較低效 – 如果你的字段要使用負數值,請使用sint64來代替。 | int64 | long | int/long[3] | int64 |
| uint32 | 使用可變長度編碼 | uint32 | int[1] | int/long[3] | uint32 |
| uint64 | 使用可變長度編碼 | uint64 | long[1] | int/long[3] | uint64 |
詳細語法由於篇章太多不在此做介紹,詳情點開另一篇博文: http://www.cnblogs.com/tohxyblog/p/8974763.html
<!-- protobuf-谷歌 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
2.2.1下載編譯器編譯文件
下載地址:https://github.com/google/protobuf/releases
選擇對應系統的版本,下載后解壓。
可以通過定義好的.proto文件來生成Java代碼,需要基於.proto文件運行protocol buffer編譯器protoc。如果你沒有安裝編譯器,下載安裝包並遵照README安裝。
通過如下方式調用protocol編譯器:
protoc -I=/Users/rinzz04/Desktop/proto/proto --java_out=/Users/rinzz04/Desktop/proto/ /Users/rinzz04/Desktop/proto/proto/InitGame.proto
-I=proto文件存放路徑- --java_out=生成的java文件夾目錄
- 后面緊跟proto具體文件
proto文件如下:
syntax = "proto3";
message User {
string userId = 1;
string userName = 2;
bool sex = 3;
string openId = 4;
string createTime = 5;
string phoneNum = 6;
string userImg = 7;
string introduct = 8;
}
2.2.3protobuf使用教程
//以user為例編碼成byte[] UserOuterClass.User.Builder userBuild = UserOuterClass.User.newBuilder(); userBuild.setUserId(user.getUserId()); userBuild.setUserName(user.getUserName()); userBuild.setPhoneNum(user.getPhoneNum()); userBuild.setCreateTime(user.getCreateTime()); userBuild.setOpenId(user.getOpenId()); userBuild.setIntroduct(user.getIntroduct()); userBuild.setSex(user.isSex()); userBuild.setUserImg(user.getUserImg()); userBuild .toByteArray();//得到byte[] //以user為例解碼 UserOuterClass.User.Builder userBuild = UserOuterClass.User.newBuilder(); User user= user.build(); user=User.parseFrom(data.getValue().getBytes());
三、protobuf在實際操作中存在的問題
protobuf主要用於與前端通信編解碼,那么在后台收到二進制如何存入到數據庫中呢,或者說從數據庫中取得的數據怎么映射到protobean呢。
由於protoc生成的java文件與我們平時寫的java文件有區別,但是實際上都是有getset方法,不怕麻煩的童鞋可以直接通過兩個類的值getset方法直接轉換,效率可觀,但是操作起來確實有些麻煩。這里我們提供一個更加便捷的工具類。
/**
* 該方法將javabean對象轉換成protobuf對應的bean
*
* @param javaBean
* @param protoBuilder
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object javaBeanToProtoBean(Object javaBean, Object protoBuilder) {
try {
Method mm = protoBuilder.getClass().getMethod("getDescriptorForType");
Descriptors.Descriptor descriptor = (Descriptor) mm.invoke(protoBuilder);
Field[] fields = javaBean.getClass().getDeclaredFields();
for (Field item : fields) {
try{
String fName = item.getName();
item.setAccessible(true);
Object jObject = item.get(javaBean);
if(null == jObject){
break;
}
FieldDescriptor fd = descriptor.findFieldByName(fName);
if(null != fd){
if(fd.isRepeated()){
boolean isDefined = false;
Method[] mmm = protoBuilder.getClass().getMethods();
for(Method mItem : mmm){
try{
String mName = mItem.getName();
String mName1 = "add" + StringUtil.firstToUpper(fName);
if(mName1.equals(mName) && mItem.getParameterTypes().length == 1){
Class[] ccList = mItem.getParameterTypes();
Class cc = ccList[0];
Method me = cc.getMethod("newBuilder");
Object oBuilder = me.invoke(null);//獲取自定義對象builder
List<Object> dList = (List<Object>) jObject; //數據為List集合
List<Object> pBeanList = new ArrayList<Object>();
for(Object oItem : dList){
Object pBean = javaBeanToProtoBean(oItem,oBuilder);
pBeanList.add(pBean);
}
Method mee = protoBuilder.getClass().getMethod("addAll"+StringUtil.firstToUpper(fName),Iterable.class);
mee.invoke(protoBuilder, pBeanList);
isDefined = true;
}
}catch(Exception e){
}
}
if(!isDefined){
try{
Method me = protoBuilder.getClass().getMethod("addAll"+StringUtil.firstToUpper(fName),Iterable.class);
me.invoke(protoBuilder, jObject);
}catch(Exception e){
logger .info("this repeated field is a user-defined field");
e.printStackTrace();
}
}
}else{
boolean isDefined1 = false;
try{
// 自定義對象繼續需要通過builder來解析處理,回調、 這一塊很占計算時間。有待優化
Method bM = protoBuilder.getClass().getMethod("getFieldBuilder", FieldDescriptor.class);
Object subBuilder = bM.invoke(protoBuilder, fd);
Object pBean = javaBeanToProtoBean(jObject,subBuilder);
Method me = protoBuilder.getClass().getMethod("setField", FieldDescriptor.class, Object.class);
me.invoke(protoBuilder, fd, pBean);
isDefined1 = true;
}catch(Exception e){
// logger .info("this required field is not a user-defined field");
}
if(!isDefined1){
Method me = protoBuilder.getClass().getMethod("setField", FieldDescriptor.class, Object.class);
me.invoke(protoBuilder, fd, jObject);
}
}
}
}catch(Exception e){
logger .error("javaBeanToProtoBean method item reflect error, item name:"+item.getName());
}
}
Method buildM = protoBuilder.getClass().getMethod("build");
Object rObject = buildM.invoke(protoBuilder);
/* Method byteM = rObject.getClass().getMethod("toByteArray");
Object byteObject = byteM.invoke(rObject);
byte[] pbByte = (byte[]) byteObject;
String pbStr = new String(Base64.getEncoder().encode(pbByte), "UTF-8");*/
return rObject;
} catch (Exception e) {
e.printStackTrace();
logger.error("convert javabean to protobuf bean error,e:", e);
return null;
}
}
以上方法可以通用的講前端發送過來的protobean轉成我們需要的普通javabean,但是在性能上比getset慢上許多,普通項目用起來是沒問題,也能達到每秒幾萬次,但是對性能有要求的童鞋可以關注我注釋的那一行代碼。
try{
// 自定義對象繼續需要通過builder來解析處理,回調、 這一塊很占計算時間。有待優化
Method bM = protoBuilder.getClass().getMethod("getFieldBuilder", FieldDescriptor.class);
Object subBuilder = bM.invoke(protoBuilder, fd);
Object pBean = javaBeanToProtoBean(jObject,subBuilder);
Method me = protoBuilder.getClass().getMethod("setField", FieldDescriptor.class, Object.class);
me.invoke(protoBuilder, fd, pBean);
isDefined1 = true;
}catch(Exception e){
// logger .info("this required field is not a user-defined field");
}
由於轉換中有這里要對包含其他bean做處理,所以在普通操作時經常進了catch代碼塊,所以浪費了很長時間(眾所周知,catch是很浪費時間的),但是去掉這塊代碼轉包含關系的bean就有問題,這塊難題暫時博主也沒解決,留給你們去,能解決的可以在下方留言。如果解決不了但是還是想簡單方便的,可以關注我的下一篇博文,protostuff。
