前言:说到JSON可能你们很熟悉,是目前应用最普遍的一种序列化格式,它使用起来简单方便,并且拥有超高的可读性。可是在愈来愈多的应用场景里,JSON冗长的缺点致使它并非一种最优的选择。html
目前JAVA经常使用的序列化有protobuf,json,xml,Serializable,hessian,kryo。他们的优缺点以下:前端
JSON:很少说了,用途普遍,序列化方式还衍生了阿里的fastjson,美团的MSON,谷歌的GSON等更加优秀的转码工具。
优势:使用方便。
缺点:数据冗长,转码性能通常。java
XML:好久以前的转码方法了,如今用的很少。
优势:暂时没发现。
缺点:数据冗长,转码性能通常。python
Serialzable:JDK自带的序列化。
优势:使用方便。
缺点:转码性能低下。git
hessian:基于 binary-RPC实现的远程通信library,使用二进制传输数据。
优势:数据长度小。
缺点:性能低下。github
说了这么多,全是性能低下,MMP一群智障儿?固然不是!kryo就是一款快速、高效的序列化框架,可是它不是咱们今天的主角,由于他只能在java中使用,和前端非java语言的通信就存在极大的隔阂。咱们今天的主角是protobuf?emmm,算是吧,可是也不全是,先给你们说下protobuf吧。数据库
优势:转码性能高,支持多语言。
缺点:中文文档少,使用相对复杂。
json
在使用protobuf以前,须要安装protobuf编译器和运行时环境。
因为protobuf是跨平台,跨语言的,因此须要下载和安装对应版本的编译器和运行时依赖。markdown
.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>
下载地址: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文件存放路径
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; }
//以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主要用于与前端通讯编解码,那么在后台收到二进制如何存入到数据库中呢,或者说从数据库中取得的数据怎么映射到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。