认识ProtoBuf

发布于:2023-01-20 ⋅ 阅读:(6) ⋅ 点赞:(0) ⋅ 评论:(0)

认识 Protocol buffers

背景

Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

Protocol buffersgoogle开发的一种数据描述语言,类似于 XML 能够将结构化数据序列化,可用于数据存储、通信协议等方面。相比于现在流行的 XML 以及 JSON 格式存储数据,通过 Protocol Buffers 来定义的文件体积更小,解析速度更快

支持语种

Language Source
C++ (include C++ runtime and protoc) src
Java java
Python python
Objective-C objectivec
C# csharp
Ruby ruby
Go protocolbuffers/protobuf-go
PHP php
Dart dart-lang/protobuf

protobuf 使用

  1. 添加插件

在module的build.gradle添加插件

plugins {
    ...
    id 'com.google.protobuf'
}

在project 的 build.gradle添加插件

dependencies {
        ...
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.19'
    }
  1. 在module中build.gradle添加依赖
implementation 'com.google.protobuf:protobuf-java:3.0.0'
  1. 在module中build.gradle添加配置
//编写编译任务,调用plugin编译生成java文件
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.21.2'//编译器版本
    }
    plugins {
        javalite {
            //指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'

        }
    }
    generateProtoTasks.generatedFilesBaseDir = "$projectDir/src/main/java" //指定编译生成java类的存放位置
    generateProtoTasks {
        all().each { task ->
            task.plugins {
                javalite {
                    outputSubDir = '' //指定存放位置的二级目录,这里未指定
                }
            }
        }
    }
}

注:javalite 和 protobuf-java 版本要匹配,否则编译不过

  1. 在项目中新建proto文件夹

在这里插入图片描述


//proto的版本
syntax = "proto3";
option java_package = "com.youdao.protobuf.model";
option java_outer_classname = "UserBeanProto";
message UserInfo{
  int32 id = 1;
  string name = 2;
  Person person = 3;
}

message Person{
  int64 age = 18;
  string name = 10;
  repeated int32 phoneNum= 12;
}
  • syntax 是proto版本号 Protobuf 有两个大版本,proto2 和 proto3

    proto3 相对 proto2 而言,简言之就是支持更多的语言(Ruby、C#等)、删除了一些复杂的语法和特性、引入了更多的约定等。

  • java_package 包名

  • java_outer_classname 类名

  • int32 java中的int类型

  • int64 java中的long类型

  • string java中的String 类型


  1. 编译之后生成代码

在这里插入图片描述


  1. 调用
    var message = UserBeanProto.Person.newBuilder()
                .setAge(18)
                .addPhoneNum(110)
                .setName("lily")
                .build()
                
     var person = UserBeanProto.Person.parseFrom(result)
    

输出结果

[82, 4, 108, 105, 108, 121, 96, 110, -112, 1, 18]

Protocol Buffers 编码


protobuf高效的秘密在于它的编码格式,它采用了 TLV(tag-length-value) 编码格式。每个字段都有唯一的 tag 值,它是字段的唯一标识。length 表示 value 数据的长度,length 不是必须的,对于固定长度的 value,是没有 length 的。value 是数据本身的内容。


![在这里插入图片描述](https://img-blog.csdnimg.cn/92ec78412b194f8280e974ba937b0697.png#pic_center)

对于 tag 值,它有 field_number 和 wire_type 两部分组成。field_number 就是在前面的 message 中我们给每个字段的编号,wire_type 表示类型,是固定长度还是变长的。 wire_type 当前有0到5一共6个值,所以用3个 bit 就可以表示这6个值

Protocol Buffers 在 3 版本中定义了 4 种类型 wire_type:

  • 0 VarInt 表示 int32, int64, uint32, uint64, sint32, sint64, bool, enum
  • 1 64-bit 表示 fixed64, sfixed64, double
  • 2 Length-delimited 表示 string, bytes, embedded messages, repeated 字段
  • 5 32-bit 表示 fixed32, sfixed32, float

对于二进制编码,经常需要对数据进行压缩以节省空间,varint可以压缩较小的正数,但是对于负数varint反而更浪费空间,zigzag编码可以处理负数,使负数也可以使用varint编码压缩,protobuf使用二者结合的方式来压缩数字类型


Varint编码

varint编码每个字节前1位表示下一个字节是否也是该数字的一部分,后7位表示实际的值,最后,先低位后高位,对于int类型来说,varint编码最少占用1个字节,最多占用5个字节。

#define VARINT_FIX (0x80)
uint8_t* varint_encode(uint32_t val, uint8_t *ptr)
{
    if (val < VARINT_FIX)
    {
        ptr[0] = (uint8_t)val;
        return ptr+1;
    }
    ptr[0] = (uint8_t)(val|VARINT_FIX);
    val >>= 7;

    if (val < VARINT_FIX)
    {
        ptr[1] = (uint8_t)val;
        return ptr+2;
    }
    ptr++;
    do
    {
        *ptr = (uint8_t)(val|VARINT_FIX);
        val >>= 7;
        ++ptr;

    }while(val >= VARINT_FIX);
    *ptr++ = (uint8_t)val;

    return ptr;
}

举例
在这里插入图片描述



Zigzag编码

对于负数来说,因为最高位符号位始终为1,使用varint编码就很浪费空间,zigzag编码就是解决负数的问题的,同时其对正数也没有很大的影响。

int类型zigzag变换的代码表示为(n << 1) ^ (n >> 31)

左移1位可以消去符号位,低位补0
有符号右移31位将符号位移动到最低位,负数高位补1,正数高位补0
按位异或
对于正数来说,最低位符号位为0,其他位不变
对于负数,最低位符号位为1,其他位按位取反


ProtoBuf编码 先进行Zigzag编码,再使用Varint编码;解码相反


与xml json 对比


在这里插入图片描述


总结

ProtoBuf 性能好,效率高,传输快,自动生成序列化反序列化代码,支持多种语言;缺点也比较明显,二进制的可读性要差一些,相对于xml和json来说通用性一般,支持的语种也一般