美文网首页protobuf
ProtoBuf在java中的实际应用

ProtoBuf在java中的实际应用

作者: 孔厌柔 | 来源:发表于2018-11-15 18:51 被阅读0次

    在实际的应用之前,我们再了解以下protobuf。
    通过比较它与其他数据格式进行比较,可以使我们更好的认识它的应用场景。下面与XML,JSON进行一个简单的比较。

    • JSON:一般在web项目中广泛使用,主要是由于浏览器的支持非常好,内部构建了与多函数来支持JSON。具有可读性。
    • XML:在WebService中广泛使用,但是过于冗余(毕竟是通过标签进行标识)。也具有可读性。
    • ProtoBuf:适合高性能,对响应速度有要求的数据传输场景。由于是二进制数据格式需要编码和解码。不具有可读性。在官网上描述ProtoBuf比XML小3到10倍,快20到100倍。

    结论: 在一个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间。


    在上面我们对ProtoBuf与其他数据格式做了比较,只简单的了解到它的小和快,可这是如何实现的呢?那么我们在下面对ProtoBuf存储原理做一个简要的介绍。以下内容转载自博文-google protobuf存储原理及一些底层api应用

    核心是Google 提出了“Base 128 Varints”编码,这是一种变字节长度的编码,官方描述为:varints是用一个或多个字节序列化整形的一种方法。

    序列化方式
    protobuf 把 message通过一系列key_value对来表示。
    Key 的算法为:(field_number << 3)| wired_type
    这里field_number 就是具体的索引,wired_type的值按下表查询。

    wired_type .proto类型
    0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
    1 64-bit fixed64, sfixed64, double
    2 Length-delimited string, bytes, embedded messages, packed repeated fields
    5 32-bit fixed32, sfixed32, float

    对于int,bool,enum类型,value就是Varint。
    而对于string,bytes,message等等类型,value是长度+原始内容编码。

    • 举例int类型存储(Varint存储原理)
      存储一个int32 类型的数字,通常是4个字节。但是Varints最少只需要一个字节就可以了。
      Varints规定小于128的数字都可以用一个字节来表示,比如10, 它就会用一个字节 0000 1010 来存储。
      对于大于128的数字,则用更多个字节存储。
      以150举例:protobuf的存储字节是 1001 0110 0000 0001。
      为什么会这样标识呢?首先我们了解一个字节共8位,表示的数字是255,但是Varints只用一个字节表示小于128的数字,换句话说,就是Varints只用了8位中的7位来表示数字,而还有一位被用来干嘛了呢?
      Varints在官方规定中表示,每个字节的最高位是有特殊含义,当最高位为1的时候,代表后续的字节也是该数字的一部分,后续为0的时候,则表示结束。
      比如过150,二进制表示为 1001 0110。
      先取后七位 001 0110, 作为第一个字节的内容。
      再取余下的位,补0凑齐7位,就是000 0001。
      对于intel机器,是小端字节序,低字节位于地址低的。0010110 是低字节地址,因此排在前面,因为后面的也是数字的一部分,所以高位补1,也就成了10010110。 同样的,高字节000 0001,排在后面,并且它后面没有后续字节了,所以补0,也就成了 0000 0001。
      因此150 在protobuf中的表示方式为 1001 0110 0000 0001。
    • 举例string类型存储
    message Test {
        required string desc = 2;
    }
    

    假如把 a 设置为 “testing”的话, 那么序列化后的就是
    12 07 74 65 73 74 69 64 67
    其中12是key。剩下的是value。
    怎么算的呢?先看12, 这里的12,是个16进制数字,其二进制位表示为 0001 0010。
    0010 就是类型string的对应的Type值,根据上表,也就是2。
    field_number 是 2,也就是0010,左移三位,就成了0001 0000。
    按照key的计算公式,和Type值取并后就变成了 0001 0010,即12。
    Value是长度加原始内容编码。
    07就是长度, 代表string总长7个字节。 后面7个数字一次代表每个字母所对应的16进制表示。


    下面通过java配合protobuf进行一些简单的应用,以下操作类请参考初识ProtoBuf,添加依赖或者jar包,此处我是通过maven项目进行演示。

    <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.6.1</version>
    </dependency>
    

    java代码展示如下:

    //创建对象,及属性赋值
    PersonProto.Person.Builder builder = PersonProto.Person.newBuilder();
    
    builder.setName("Mrzhang")
            .setAge(18)
            .setSex(true)
            .setBirthday(System.currentTimeMillis())
            .setAddress("军事基地")
            .addCars(0, PersonProto.Car.newBuilder().setName("兰博基尼").setColor("Red").build())
            .putOther("描述", "暂无");
    PersonProto.Person person = builder.build();
    
    //序列化(通过protobuf生成的java类的内部方法进行序列化)
    byte[] bytes = person.toByteArray();
    
    //反序列化(通过protobuf生成的java类的内部方法进行反序列化)
    try {
            PersonProto.Person parseFrom = PersonProto.Person.parseFrom(bytes);
    } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
    }
    

    我们可以将此对象的序列化byte[]传送给其他服务,让他们通过同一个.proto文件生成相应语言的文件,通过内部的方法进行反序列化。

    举例

    • 两个相互独立的项目,但是会操作同一数据,并且数据进行缓存,可以将序列化byte[]存放在Redis
    • 不同语言通过的数据通信

    ProtoBuf与Json的转换
    为什么要做这样的转换呢?因为我想到与数据库对应的javabean是否可以转换为proto数据这样就可以进行数据进行后续的数据交互了。(当然也有其他方式,例如反射,和复制属性等方式)

    //通过fastJson进行转换
    String json = JsonUtil.toJson(person);
    

    让人意外的是,转换失败。提示信息一大堆,大致意思就是不能够转换。
    通过ProtoBuf Util转换
    添加依赖

    <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>3.6.1</version>
    </dependency>
    

    java代码展示如下:

    //to Json
    JsonFormat.Printer printer = JsonFormat.printer();
    String print = "";
    try {
        print = printer.print(person);
        System.out.println(print);
    } catch (InvalidProtocolBufferException e) {
        e.printStackTrace();
    }
    
    //to Object
    JsonFormat.Parser parser = JsonFormat.parser();
    try {
        PersonProto.Person.Builder newBuilder = PersonProto.Person.newBuilder();
        parser.merge(print, newBuilder);
        System.out.println(newBuilder.build());
    } catch (InvalidProtocolBufferException e) {
        e.printStackTrace();
    }
    
    //添加java bean 此类对性数据库的字段,同时与proto类属性名相同
    public class Person implements Serializable {
        private String name;
        private Integer age;
        private Boolean sex;
        private Date dirthday;//此处注意这里是时间类型而非proto类中的long类型
        private String address;
        private List<Car> cars = new ArrayList<Car>();
        private Map<String, String> other = new HashMap<String, String>();
    }
    
    public class Car implements Serializable {
        private String name;
        private String color;
    }
    
    //在上面的转换中间添加以下代码,发现同样转换成功
    Person myPerson = JsonUtil.toObject(print, Person.class);
    System.out.println(myPerson);
    print = JsonUtil.toJson(myPerson);
    

    总结:通过上面的测试,我们发现可以实现自定义的bean与proto是可以通过Json相互转换的,然而它们之间的转换需要第三方JSON转换工具和protobuf util的支持。
    上面可以应用于自定义的bean主要用于web前后台的交互,同时通过JSON转换也可以进行与其他服务的交互,数据可以放在redis中。
    当然还有其他的转换方式,如我上面所说:反射,和复制属性。
    反射可以通过属性名称一一对应进行转换,在特定的情况下也是可以通过属性类型进行转换。
    复制属性,可以通过Spring的BeanUtils.copyProperties(Object source, Object target)方法进行转换的。
    后期我将补充此转换方式的案例。

    相关文章

      网友评论

        本文标题:ProtoBuf在java中的实际应用

        本文链接:https://www.haomeiwen.com/subject/uuiefqtx.html