这一系列文章主要是对protocol buffer这种编码格式的使用方式、特点、使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务。

在上一篇文章中,我们完整了解了protobuf的编码原理,那么在这篇文章中,我将会展示在使用过程中遇到的问题,以及解决方案。并在此基础上根据我们实际的使用场景进行改进。

本文主要涉及以下2个部分

1.protobuf的使用背景及所遇到的问题

2.自己完成一个protobuf的编码、解码类库,兼容官方的编码过程

protobuf的使用背景

我在日常工作中是进行APP服务端开发的,服务端与客户端的数据交互格式使用的是最常用的json。

众所周知,在移动互联网的使用场景下,单次请求耗时对于用户来说是一个非常敏感的数据指标,而影响单次请求耗时的因素有很多,其中最重要的自然是服务端的数据处理能力与网络信号的状态。服务端的处理数据处理能力是完全在我们自己的掌控之中,可以有很多方法提高响应速度。然而用户的网络信号状态是我们无法控制的,也许是3G信号,也许是4G信号,也许正在经过一个隧道,也许正在地下商场等等。如果我们能降低每一次网络请求的数据量,那么也算是在我们所能掌控的范围内去优化请求响应时长的问题了。

在我接触到protobuf之后,了解到其编码后的字节数量会比json小许多,就开始思考有没有可能在移动互联网场景下使用protobuf代替json格式。网上搜索了一下之后发现并没有相关内容,于是就着手以自己工作中的APP为基础进行protobuf的实际应用探索。(当然grpc也是一种选项,不过改造成本比较大,我这里只考虑对编码方式进行改进)

使用阶段一:直接使用原生类库

在第一阶段中,自然是考虑直接使用google提供的各版本类库。在服务端和android端使用的是java版本的类库,而ios端使用的是swift类库。

在系列的第一篇文章中,已经展示java类库的使用流程。在此过程中我们会发现,我们定义好.proto文件后,需要使用google提供的编译器来生成相应的.java模型文件。而即使是一个简单的模型都会生成一个庞大的.java文件,原因在之前编码原理的文章中都有提及,即protobuf为了减少编码后的字节数,抛弃了很多数据相关的信息(因此protobuf是一个不可以自解释的编码方式),因此为了实现信息的正确编码和解码,信息的发送方和接收方都必须拥有同一个定义好的.java文件,该java文件需要包含完整的编码解码逻辑

对于服务端来说,模型文件的大小并不是一个大的问题,然而对于android客户端来说,这却是非常致命的。在移动互联网场景下,单次请求的时长对于用户来说很敏感,而客户端的大小对于用户来说也是一个不可忽略的问题。特别在很多线下业务推广场景下,需要客户当场下载APP,此时客户端的下载速度将会极大地影响推广的成功率(想象一下,如果一个app有200MB,在非wifi情况下,很多用户应该都会犹豫的吧。即使在wifi情况下,1分钟下载完毕和2分钟下载完毕对于用户的体验上也是天壤之别)。

在我的实际使用中,仅仅一个略复杂的.java模型文件会达到800kb!!而整个APP包含的模型文件何止百个,如果完全使用原生类库,android客户端的大小将成为一个灾难。

而对于ios客户端来说,情况相对好一些,不过类库本身的大小也达到了10MB,基于同样的原因,这也并不是一个可以接受的方案。

因此需要解决的第一个问题就是原生类库大小的问题。

原生类库大小解决方案

首先,我们需要分析protobuf官方.java文件巨大的原因。

正如之前提到的,因为protobuf是一个不可自解释的数据格式,特别是不同的数据内容编码后的结果可以是完全相同的(参见上一篇文章最后的例子),所以需要在编译器生成的.java文件中包含定制的编码、解码逻辑,以将相同的编码结果对应到不同的java类型上。

我们摘取一段protobuf生成的.java文件中的分支代码,其中的tag正是表示序号和类型的字节,所以在编码与解码的时候就是根据这个字节的值进入不同的case分支,进行数据的读取和写入。所以对于protobuf的官方类库而言,表示序号和类型的字节是灵魂,因为这个字节一旦发生了变化,编码的结果将完全不同。

...
int tag = input.readTag();
switch (tag) {
case 0:
done = true;
break;
case 8: {
age_ = input.readInt32();
break;
}
case 16: {
hairCount_ = input.readInt64();
break;
}
case 24: {
isMale_ = input.readBool();
break;
}
case 34: {
java.lang.String s = input.readStringRequireUtf8();
name_ = s;
break;
}
...
}
...

并且为了实现跨平台、跨语言地使用,protobuf所依赖的模型定义是.proto文件,而.java文件仅仅是根据.proto定义所生成的,并非是模型的原始定义。为了摆脱.proto的束缚,我们还必须将模型的定义直接放到.java文件中。

例如我们原先定义.proto文件如下

syntax = "proto3";

option java_package = "cn.tera.protobuf.model";
option java_outer_classname = "BasicUsage"; message Person {
string name = 1;
int32 id = 2;
string email = 3;
}

现在直接将其定义到.java文件中,且抛弃了outer_classname

package cn.tera.protobuf.model;

public class Person {
String name;
int id;
String email;
}

接着就需要考虑这样一个问题,之前一直在强调,.proto中定义的字段的序号和类型是protobuf的灵魂,然而此时我们同时抛弃了.proto的定义和编译器生成的定制化.java文件,那又该如何去确定字段的序号和类型呢?

答案是依赖定义的java模型本身。

java语言自身其实就是一个强类型的语言,它在编码和解码的过程中,完全可以知晓每一个字段的数据类型,而不需要根据.proto文件生成各种定制的逻辑。

而序号问题我们可以通过一些约定,例如字段名的小写字母顺序进行排序。

既然解决了protobuf的核心依赖问题,那么接着就可以着手编写编码和解码的类库了

先看编码部分的功能,我们将其定义为BasicEncoder。

public class BasicEncoder {
}

在使用的时候为了简化和直观,我们定义入口方法的形式如下

public class BasicEncoder {
public static <T> byte[] serialize(T obj, Class<T> clazz) {
...
}
}

因为很多时候涉及到子对象的写入,因此需要做递归的调用,那么我们就再包一层writeObject方法

public class BasicEncoder {
public static <T> byte[] serialize(T obj, Class<T> clazz) {
//主逻辑函数,为了方便递归调用
List<Byte> bytes = writeObject(0, obj, clazz);
//将List转换成Array
byte[] result = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); i++) {
result[i] = bytes.get(i);
}
return result;
}
}

接着我们就来看writeObject方法

/**
* 主逻辑方法
*
* @param o 序号,当第一次被调用时会传入0
* @param obj 模型实例
* @param clazz 模型类
* @param <T> 泛型
* @return
*/
public static <T> List<Byte> writeObject(int o, T obj, Class<T> clazz) {
//结果字节,因为在编码结束前是不确定总大小的,因此用List来作为返回参数
List<Byte> bytes = new ArrayList<>();
try {
List<Field> fields = Helper.getAllFields(clazz);
Map<Integer, Field> fieldList = Helper.sortFields(fields);
List<Integer> fieldNums = fieldList.keySet().stream().collect(Collectors.toList());
fieldNums.sort(Comparator.comparing(f -> f));
for (int order : fieldNums) {
Field f = fieldList.get(order);
f.setAccessible(true);
Object value = f.get(obj);
if (value != null) {
if (value instanceof String) {
bytes.addAll(writeString(order, (String) value));
} else if (value instanceof Boolean) {
bytes.addAll(writeBoolean(order, (Boolean) value));
} else if (value instanceof Integer) {
bytes.addAll(writeInt32(order, (Integer) value));
} else if (value instanceof Double) {
bytes.addAll(writeFixed64(order, (Double) value));
} else if (value instanceof Float) {
bytes.addAll(writeFixed32(order, (Float) value));
} else if (value instanceof Long) {
bytes.addAll(writeInt64(order, (Long) value));
} else if (value instanceof List) {
bytes.addAll(writeList(order, (List) value));
} else {
Class c = f.getType();
bytes.addAll(writeObject(order, f.get(obj), c));
}
}
order++;
}
//序号+类型字节
List<Byte> headBytes = new ArrayList<>();
if (o != 0) {
headBytes.addAll(writeTag(o, 2));
}
if (headBytes.size() > 0) {
headBytes.addAll(writeUInt32NoTag(bytes.size()));
bytes.addAll(0, headBytes);
}
} catch (Exception e) {
System.out.println(e);
}
return bytes;
}

首先我们自然要取出该类的所有字段,包括其父类的字段

List<Field> fields = Helper.getAllFields(clazz);

接着对字段做一个排序,将其按照小写字母的顺序进行排序,并将序号和对应的字段做一个map

Map<Integer, Field> fieldList = Helper.sortFields(fields);

对序号进行一个排序

List<Integer> fieldNums = fieldList.keySet().stream().collect(Collectors.toList());
fieldNums.sort(Comparator.comparing(f -> f));

根据序号的顺序,遍历所有的字段,然后根据字段的类型写入数据。注意最后一个else,就是一个对于子对象的递归调用

for (int order : fieldNums) {
Field f = fieldList.get(order);
f.setAccessible(true);
Object value = f.get(obj);
if (value != null) {
if (value instanceof String) {
bytes.addAll(writeString(order, (String) value));
} else if (value instanceof Boolean) {
bytes.addAll(writeBoolean(order, (Boolean) value));
} else if (value instanceof Integer) {
bytes.addAll(writeInt32(order, (Integer) value));
} else if (value instanceof Double) {
bytes.addAll(writeFixed64(order, (Double) value));
} else if (value instanceof Float) {
bytes.addAll(writeFixed32(order, (Float) value));
} else if (value instanceof Long) {
bytes.addAll(writeInt64(order, (Long) value));
} else if (value instanceof List) {
bytes.addAll(writeList(order, (List) value));
} else {
Class c = f.getClass();
bytes.addAll(writeObject(order, f.get(obj), c));
}
}
}

上面这一段if else解决了protobuf的类型依赖性

接着需要判断这次数据写入是否是一个子对象。因为如果是子对象的话,它除了自身的数据,还需要根据数据长度写入自身的序号、类型和数据长度。

//序号+类型字节
List<Byte> headBytes = new ArrayList<>();
//如果是第一次调用writeObject方法,o就是0,说明是主对象的写入,那就不需要序号和类型了
if (o != 0) {
headBytes.addAll(writeTag(o, 2));
}
if (headBytes.size() > 0) {
headBytes.addAll(writeUInt32NoTag(bytes.size()));
bytes.addAll(0, headBytes);
}

writeUInt32NoTag方法是从google官方类库中提取出来的

整个数据的写入过程其实并不复杂,接着我们来细看每一个方法内部逻辑是怎样的

getAllFields方法,获取所有字段

这里涉及到一个Ignore的注解,用来忽略不需要被编码的字段

/**
* 获取所有有效字段
*
* @param clazz
* @return
*/
public static List<Field> getAllFields(Class clazz) {
List<Field> fields = new ArrayList<>();
//需要循环查找父类的字段
while (clazz != null && !clazz.equals(Object.class)) {
//这里需要所有的字段,包括private的
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
//过滤ignore字段
fields.removeIf(f -> {
Ignore ignore = f.getAnnotation(Ignore.class);
return ignore != null;
});
return fields;
}

sortFields方法,根据字段名的小写值进行排序

这里涉及到一个Version注解,需要解决一个原生APP的版本兼容问题。因为某个版本的APP的客户端在发布之后是无法对代码进行更新的(当然现在有一些热更新技术,不过一般也不会涉及到模型的变更这种基础的东西)。

例如我们发布了1.0版本的客户端,某个服务端接口返回3个字段

当发布2.0版本客户端时,该接口需要新增一个返回字段,而1.0版本的客户端是无法更新到该新增字段的,如果不加以兼容,那么老版本的客户端很有可能就会无法解析接口的返回数据。所以定义了Version注解,进行排序时会优先将同一批Version的字段放到一起

public static Map<Integer, Field> sortFields(List<Field> fields) {
Map<Integer, Field> result = new HashMap<>();
List<Field> sortedFields = new ArrayList<>();
//根据Version注解对字段进行分组
Map<Integer, List<Field>> groups = Helper.groupBy(fields, f -> {
Version sort = f.getAnnotation(Version.class);
if (sort == null) {
return -1;
} else {
return sort.value();
}
});
//对分组后的Version进行排序,从小到大
List<Integer> sorts = groups.keySet().stream().collect(Collectors.toList());
sorts.sort(Comparator.comparing(f -> f));
//同一个分组的字段将会被放在一起,其内部还是按照小写的字段名进行排序
for (int s : sorts) {
groups.get(s).sort(Comparator.comparing(f -> f.getName().toLowerCase()));
sortedFields.addAll(groups.get(s));
}
//最后将所有的字段按照顺序放入map
int fieldNum = 1;
for (Field field : sortedFields) {
result.put(fieldNum++, field);
}
return result;
}

上面这2个方法解决了protobuf中的序号依赖性

接着我们来看下每一个java类型的数据究竟是如何被写入的

writeString方法,写入String类型的数据

public static List<Byte> writeString(int order, String value) {
List<Byte> bytes = new ArrayList<>();
if (value == null || value.isEmpty()) {
return bytes;
}
bytes.addAll(writeTag(order, 2));
bytes.addAll(writeStringNoTag(value));
return bytes;
}

这里涉及到2个方法

writeTag方法,就是写入序号和类型,order是传入的,而2则是protobuf定义的String类型的Type

writeStringNoTag方法,就是写入String的值,这个方法是从protobuf的官方类库中提取出来的

writeBoolean方法,写入Boolean类型的数据

public static List<Byte> writeBoolean(int order, Boolean value) {
List<Byte> bytes = new ArrayList<Byte>();
if (value == null || !value) {
return bytes;
}
bytes.addAll(writeTag(order, 0));
bytes.add((byte) 1);
return bytes;
}

这里会多做一个判断,如果value值是false,那么就不用写入数据了

因为Boolean在protobuf中的类型为Varint,所以writeTag写入的类型就是0

writeInt32和writeInt64方法,写入int和long类型的数据

public static List<Byte> writeInt32(int order, int value) {
List<Byte> result = new ArrayList<>();
if (value == 0) {
return result;
}
result.addAll(writeTag(order, 0));
result.addAll(writeInt32NoTag(value));
return result;
} public static List<Byte> writeInt64(int order, long value) {
List<Byte> result = new ArrayList<>();
if (value == 0L) {
return result;
}
result.addAll(writeTag(order, 0));
result.addAll(writeUInt64NoTag((value)));
return result;
}

因为int32和int64在protobuf中的类型为Varint,所以writeTag写入的类型就是0

这里的writeInt32NoTag和writeUInt64NoTag方法是从google的官方类库中提取出来的

writeFixed32和writeFixed64方法,写入float和double类型的数据

public static List<Byte> writeFixed64(int order, Double value) {
List<Byte> bytes = new ArrayList<Byte>();
if (value == null || value == 0) {
return bytes;
}
bytes.addAll(writeTag(order, 1));
bytes.addAll(writeFixed64NoTag(Double.doubleToRawLongBits(value)));
return bytes;
} public static List<Byte> writeFixed32(int order, Float value) {
List<Byte> bytes = new ArrayList<Byte>();
if (value == null || value == 0) {
return bytes;
}
bytes.addAll(writeTag(order, 5));
bytes.addAll(writeFixed32NoTag(Float.floatToRawIntBits(value)));
return bytes;
}

这里特别注意,调用了java的2个native方法,将float和double类型转换为IEEE754标准的二进制的形式

因为float和double对应的protobuf中的类型为32-bit和64-bit,所以writeTag写入的类型分别是5和1

writeFixed64NoTag和writeFixed32NoTag方法是从google的官方类库中提取出来的

writeList方法,写入List类型的数据

public static List<Byte> writeList(int order, List value) {
List<Byte> bytes = new ArrayList<>();
if (value != null && value.size() > 0) {
Object v = value.get(0);
if (v instanceof String) {
bytes.addAll(writeStringList(order, value));
} else if (v instanceof Boolean) {
bytes.addAll(writeNoStringList(order, value, Boolean.class));
} else if (v instanceof Integer) {
bytes.addAll(writeNoStringList(order, value, Integer.class));
} else if (v instanceof Double) {
bytes.addAll(writeNoStringList(order, value, Double.class));
} else if (v instanceof Float) {
bytes.addAll(writeNoStringList(order, value, Float.class));
} else if (v instanceof Long) {
bytes.addAll(writeNoStringList(order, value, Long.class));
} else if (v instanceof List) {
bytes.addAll(writeList(order, (List) v));
} else {
bytes.addAll(writeObjectList(order, value));
}
}
return bytes;
}

对于List对象,自然是要根据其具体持有对象的类型进行区分

对于非String类型的对象,统一会调用writeNoStringList方法

writeNoStringList方法

public static <T> List<Byte> writeNoStringList(int order, List list, Class<T> clazz) {
List<Byte> bytes = new ArrayList<>();
bytes.addAll(writeTag(order, 2));
List<Byte> contentBytes = new ArrayList<>();
for (Object d : list) {
if (clazz.equals(Double.class)) {
contentBytes.addAll(writeFixed64NoTag(Double.doubleToRawLongBits((Double) d)));
} else if (clazz.equals(Float.class)) {
contentBytes.addAll(writeFixed32NoTag(Float.floatToRawIntBits((Float) d)));
} else if (clazz.equals(Integer.class)) {
contentBytes.addAll(writeInt32NoTag((Integer) d));
} else if (clazz.equals(Long.class)) {
contentBytes.addAll(writeUInt64NoTag((Long) d));
} else if (clazz.equals(Boolean.class)) {
contentBytes.add((byte) (((Boolean) d) ? 1 : 0));
}
}
bytes.addAll(writeUInt32NoTag(contentBytes.size()));
bytes.addAll(contentBytes);
return bytes;
}

这里就根据不同的数据类型,调用google提供的类库方法进行数据写入,和非list的写入方式一致

因为List类型对应的是protobuf中的repeated类型,所以写入tag的时候固定为2

这里的writeFixed64NoTag、writeFixed32NoTag、writeInt32NoTag、writeUInt64NoTag都是从google的类库中提取出来的底层方法。

而对于String类型的List,则调用writeStringList方法

writeStringList方法

public static List<Byte> writeStringList(int fieldNumber, List list) {
List<Byte> bytes = new ArrayList<>();
for (Object s : list) {
bytes.addAll(writeString(fieldNumber, (String) s));
}
return bytes;
}

循环List中的对象,通过writeString方法写入字符串信息

对于Object类型的List,则调用writeObjectList

writeObjectList方法

public static List<Byte> writeObjectList(int fieldNumber, List list) {
List<Byte> bytes = new ArrayList<>();
for (Object o : list) {
Class c = o.getClass();
bytes.addAll(writeObject(fieldNumber, o, c));
}
return bytes;
}

在这里就会循环List中的元素,递归调用writeObject方法

上述代码就是我们类库中的编码的主要逻辑。

其实解码的逻辑和编码是非常类似的,不过限于篇幅就不全部贴上来了,有兴趣的同学可以去git上查看,上面也包含了之前几篇文章的所有测试代码和.proto文件

https://github.com/TeraTian/optimized-protobuf

接着我们看一下这个类库的使用示例

/**
* 类库的基本使用方式
*/
@Test
public void basicEncoderTest() {
String source = "{\"score2\":13213.1231,\"age\":5,\"name\":\"Peter\",\"hairCount\":183728182371871131,\"isMale\":true,\"score\":13213.1231}";
test(source, Student.class, ProtobufStudent.Student.class);
}

test方法

/**
* test method
*
* @param source model json
* @param javaClass java class
* @param protobufClass protobuf class
*/
static <T, P extends Message> void test(String source, Class<T> javaClass, Class<P> protobufClass) {
try {
System.out.println("------------------- source json --------------------");
System.out.println(source);
System.out.println("count:" + source.getBytes().length);
System.out.println();
System.out.println("-------------------protobuf encode result-------------------");
Message.Builder builder = (Message.Builder) protobufClass.getMethod("newBuilder").invoke(null);
byte[] protoBytes = Helper.protobufSerialize(source, builder);
Helper.printBytes(protoBytes);
builder.mergeFrom(protoBytes); System.out.println();
System.out.println("------------------- tera encode result -------------------");
T javaModel = JSON.parseObject(source, javaClass);
byte[] teraBytes = BasicEncoder.serialize(javaModel, javaClass);
Helper.printBytes(teraBytes); System.out.println();
System.out.println("------------------- bytes compare result -------------------");
System.out.println(Helper.compareBytes(protoBytes, teraBytes)); System.out.println();
System.out.println("------------------- tera decode result -------------------");
T deserialJavaModel = new BasicDecoder().deserialize(teraBytes, javaClass);
System.out.println(JSON.toJSON(deserialJavaModel)); } catch (Exception e) {
System.out.println(e.getMessage());
}
}

输出结果

-------------------     source json     --------------------
{"score2":13213.1231,"age":5,"name":"Peter","hairCount":183728182371871131,"isMale":true,"score":13213.1231}
count:108 -------------------protobuf encode result-------------------
8 5 16 -101 -45 -125 -84 -17 -7 -82 -58 2 24 1 34 5 80 101 116 101 114 41 18 -91 -67 -63 -113 -50 -55 64 53 126 116 78 70
count:35 ------------------- tera encode result -------------------
8 5 16 -101 -45 -125 -84 -17 -7 -82 -58 2 24 1 34 5 80 101 116 101 114 41 18 -91 -67 -63 -113 -50 -55 64 53 126 116 78 70
count:35 ------------------- bytes compare result -------------------
true ------------------- tera decode result -------------------
{"score":13213.1231,"isMale":true,"score2":13213.123,"hairCount":183728182371871131,"name":"Peter","age":5}

可以看到对于这样一个数据结构,protobuf编码后为35个字节,而json则需要108个字节

接着比较了protobuf原生类库的编码结果和我自己完成类库的编码结果,是一致的。

当然,如果需要和原生protobuf兼容的话,需要将protobuf中字段的序号按照小写字母的顺序进行定义。不过开发该类库的目的并非是代替已经存在的protobuf原生类库,而是为了更方便地将数据格式从json切换到protobuf,所以原先考虑过定义Tag注解来强行指定字段的序号,不过觉得意义不大

例如之前定义的Student.proto,我们修改一下其中的字段顺序(暂时还不支持enum,所以去掉了Color)

syntax = "proto3";

option java_package = "cn.tera.protobuf.coder.models.protobuf";
option java_outer_classname = "CoderTestModel"; message Student{
int32 age = 1;
Parent father = 2;
repeated string friends = 3;
int64 hairCount = 4;
double height = 5;
repeated Hobby hobbies = 6;
bool isMale = 7;
Parent mother = 8;
string name = 9;
float weight = 10;
} message Parent {
int32 age = 1;
string name = 2;
} message Hobby {
int32 cost = 1;
string name = 2;
}

接着我们定义相应的java模型

package cn.tera.protobuf.coder.models.java;

import java.util.List;

public class CoderTestStudent {
public int age;
public Parent father;
public List<String> friends;
public long hairCount;
public double height;
public List<Hobby> hobbies;
public boolean isMale;
public Parent mother;
public String name;
public float weight; public class Parent {
public int age;
public String name;
} public class Hobby {
public int cost;
public String name;
}
}

json内容

{
"age": 13,
"father": {
"age": 45,
"name": "Tom"
},
"friends": ["mary", "peter", "john"],
"hairCount": 342728123942,
"height": 180.3,
"hobbies": [{
"cost": 130,
"name": "football"
}, {
"cost": 270,
"name": "basketball"
}],
"isMale": true,
"mother": {
"age": 45,
"name": "Alice"
},
"name": "Tera",
"weight": 52.34
}

测试代码

/**
* 一个相对复杂的模型测试
*/
@Test
public void complexModelTest() {
String source = "{\"age\":13,\"father\":{\"age\":45,\"name\":\"Tom\"},\"friends\":[\"mary\",\"peter\",\"john\"],\"hairCount\":342728123942,\"height\":180.3,\"hobbies\":[{\"cost\":130,\"name\":\"football\"},{\"cost\":270,\"name\":\"basketball\"}],\"isMale\":true,\"mother\":{\"age\":45,\"name\":\"Alice\"},\"name\":\"Tera\",\"weight\":52.34}";
test(source, CoderTestStudent.class, CoderTestModel.Student.class);
}

输出结果

-------------------     source json     --------------------
{"age":13,"father":{"age":45,"name":"Tom"},"friends":["mary","peter","john"],"hairCount":342728123942,"height":180.3,"hobbies":[{"cost":130,"name":"football"},{"cost":270,"name":"basketball"}],"isMale":true,"mother":{"age":45,"name":"Alice"},"name":"Tera","weight":52.34}
count:271 -------------------protobuf encode result-------------------
8 13 18 7 8 45 18 3 84 111 109 26 4 109 97 114 121 26 5 112 101 116 101 114 26 4 106 111 104 110 32 -90 -52 -64 -31 -4 9 41 -102 -103 -103 -103 -103 -119 102 64 50 13 8 -126 1 18 8 102 111 111 116 98 97 108 108 50 15 8 -114 2 18 10 98 97 115 107 101 116 98 97 108 108 56 1 66 9 8 45 18 5 65 108 105 99 101 74 4 84 101 114 97 85 41 92 81 66
count:102 ------------------- tera encode result -------------------
8 13 18 7 8 45 18 3 84 111 109 26 4 109 97 114 121 26 5 112 101 116 101 114 26 4 106 111 104 110 32 -90 -52 -64 -31 -4 9 41 -102 -103 -103 -103 -103 -119 102 64 50 13 8 -126 1 18 8 102 111 111 116 98 97 108 108 50 15 8 -114 2 18 10 98 97 115 107 101 116 98 97 108 108 56 1 66 9 8 45 18 5 65 108 105 99 101 74 4 84 101 114 97 85 41 92 81 66
count:102 ------------------- bytes compare result -------------------
true

java的类库编写和示例就到此为止。在下一篇文章中,将会展示swift的类库代码,并通过一个的http请求验证其可行性。

另外再根据原生APP的使用特性,在基本类库的基础上再次优化请求数据的大小,对于有些场景可以缩小到20%

本文总结

在移动互联网场景下使用protobuf可以减少单次请求的数据量。

使用google提供的原生类库会使得客户端的体积变大,因此无法直接应用

利用java强类型语言的特点,完成了自己编写的类库,使得编码、解码的流程完全摆脱对.proto文件的依赖,工作中怎么使用json,就可以怎么使用protobuf了

google protocol buffer——protobuf的问题及改进一的更多相关文章

  1. google protocol buffer——protobuf的基本使用和模型分析

    这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 1.什么是protobuf ...

  2. google protocol buffer——protobuf的使用特性及编码原理

    这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们展示了 ...

  3. google protocol buffer——protobuf的问题和改进2

    这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们举例了 ...

  4. google protocol buffer——protobuf的编码原理二

    这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们主要通 ...

  5. Google Protocol Buffer

    Google Protocol Buffer(protobuf)是一种高效且格式可扩展的编码结构化数据的方法.和JSON不同,protobuf支持混合二进制数据,它还有先进的和可扩展的模式支持.pro ...

  6. 前端后台以及游戏中使用Google Protocol Buffer详解

    前端后台以及游戏中使用Google Protocol Buffer详解 0.什么是protoBuf protoBuf是一种灵活高效的独立于语言平台的结构化数据表示方法,与XML相比,protoBuf更 ...

  7. Google Protocol Buffer 的使用和原理[转]

    本文转自: http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构 ...

  8. Google Protocol Buffer 的使用

    简介 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 ...

  9. Google Protocol Buffer的安装与.proto文件的定义

    什么是protocol Buffer呢? Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准. 我理解的就是:它是一种轻便高效的结构 ...

随机推荐

  1. mybatis(mysql)代码生成器扩展

    前些天在做我的KSF框架的时候需要用到mybatis代码生成器, 但是发现有一些东西需要调整,主要集中在以下几点: 1. 加入batchInsert  2. 加入batchUpdate 3. mysq ...

  2. 伸展树(Splay)学习笔记

    二叉排序树能够支持多种动态集合操作,它可以被用来表示有序集合,建立索引或优先队列等.因此,在信息学竞赛中,二叉排序树应用非常广泛. 作用于二叉排序树上的基本操作,其时间复杂度均与树的高度成正比,对于一 ...

  3. Vue老项目支持Webpack打包

    1.老的vue项目支持webpack打包 最近在学习Vue.js.版本是2.6,webpack的版本也相对较老,是2.1.0版本.项目脚手架只配置了npm run dev和npm run build. ...

  4. 【计算机算法设计与分析】——NP

    时间复杂度 时间复杂度并不是表示一个程序解决问题需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长得有多快.也就是说,对于高速处理数据的计算机来说,处理某一个特定数据的效率不能衡量一个程序的 ...

  5. 栈及其简单应用(python代码)

    栈属于线性结构(Linear Struncture),要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质."栈“者,存储货物或供旅客住宿的地方,可引申为仓库.中转站,所以引入到计 ...

  6. C#LeetCode刷题之#169-求众数(Majority Element)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4048 访问. 给定一个大小为 n 的数组,找到其中的众数.众数是 ...

  7. Newbe.Claptrap 框架如何实现 Claptrap 的多样性?

    Newbe.Claptrap 框架如何实现 Claptrap 的多样性?最近整理了一下项目的术语表.今天就谈谈什么是 Claptrap Design 和 Claptrap Factory. 特别感谢  ...

  8. Eclipse的Servers中无法添加Tomcat6/7

    2017年03月06日 17:14:46 阅读数:1007 Eclipse中在添加tomcat时发现6和7点击后发现ServerName是灰色的不能使用,也点不了NEXT,在各种查百度后发现需要删除w ...

  9. python设计模式之命令模式

    python设计模式之命令模式 现在多数应用都有撤销操作.虽然难以想象,但在很多年里,任何软件中确实都不存在撤销操作.撤销操作是在1974年引入的,但Fortran和Lisp分别早在1957年和195 ...

  10. Mybatis如何在插入(ID是后台生成的)后返回ID?

    获得ID方法: