[Protobuf]常见数据类型以及使用注意事项

[Protobuf]常见数据类型以及使用注意事项
@水墨不写bug



文章目录


一、基本数据类型

protobuf 支持多种基础数据类型,常用的有:

类型 说明 可选值范围(部分)
double 64位浮点数 ±1.7E±308
float 32位浮点数 ±3.4E±38
int32 使用变长编码[1]。负数的编码效率较低⸺若字段可能为负值,应使用 sint32 代替 -2^31 到 2^31-1
int64 使用变长编码[1]。负数的编码效率较低⸺若字段可能为负值,应使用 sint64 代替 -2^63 到 2^63-1
uint32 无符号32位整型 ,使用变长编码[1] 0 到 2^32-1
uint64 无符号64位整型 ,使用变长编码[1] 0 到 2^64-1
sint32 有符号32位整型(高效编码负数) -2^31 到 2^31-1
sint64 有符号64位整型(高效编码负数) -2^63 到 2^63-1
fixed32 定长 4 字节。若值常大于2^28 则会比 uint32 更高效。 0 到 2^32-1
fixed64 定长 8 字节。若值常大于2^56 则会比 uint64 更高效。 0 到 2^64-1
sfixed32 固定32位有符号整型(固定长度编码) -2^31 到 2^31-1
sfixed64 固定64位有符号整型(固定长度编码) -2^63 到 2^63-1
bool 布尔值 true/false
string 包含 UTF-8 和 ASCII 编码的字符串,长度不能超过 2^32 任意长度字符串
bytes 可包含任意的字节序列但长度不能超过 2^32 任意长度字节数组

注:[1] 变长编码是指:经过protobuf 编码后,原本4字节或8字节的数可能会被变为其他字节数


1、字段

在定义一个字段之后,会给字段设置一个唯一标记值同一个消息体内,字段标记值不能重复。

proto 复制代码
message PersonInfo
{
	int id = 1;
	string name = 1;//(字段标志重复)
}

消息体内的消息体可以重新计算标记值:

proto 复制代码
message PersonInfo
{
	int id = 1;
	string name = 2;
	message Phone
	{
		string number = 1;//(重新计算标记值)
	}
}

其次:

字段名称命名规范:全小写字母,多个字母之间用 _ 连接。

字段类型分为:基本数据类型 和 特殊类型(包括枚举enum、消息类型message等)。

字段唯⼀编号:用来标识字段,⼀旦开始使用就不能够再改变

字段编号的范围为:

1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可用

范围为 1 ~ 15 的字段编号需要一个字节进行编码, 16 ~ 2047内的数字需要两个字节进行编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以 1 ~ 15要用来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留一些出来。

2、字段的修饰规则

singular :消息中可以包含该字段零次或⼀次(不超过⼀次)。 proto3 语法中,字段默认使用该规则。
repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。


二、自定义数据类型

1、message类型

一个message类型被protoc编译器编译之后形成对应class。message可以嵌套message,同一层message,字段编号不能重复。message可作为类型来使用,对应class类型组合。

2、enum类型

定义enum类型的值,直接用名称+常量表示:

例如:

c 复制代码
enum PhoneType //枚举名称的名称建议用驼峰命名
{
	MP = 0;
	TEL = 1;
}

常量值从0开始,只能为正值,不能为负。枚举类型也可定义在message体内部。

在同一层级内,不同的枚举类型内的同名常量值不能相同:

例如:

c 复制代码
enum PhoneType
{
	MP = 0;
	TEL = 1;
}
enum PhoneTypeCopy
{
	MP = 0;
}

否则会报错重定义。

改为不重名即可:

c 复制代码
enum PhoneType
{
	MP = 0;
	TEL = 1;
}
enum PhoneTypeCopy
{
	MP_C = 0;
}

类似的还有两个没有限制package的文件,import后也会出现上述类似问题。
解决方法是加上package限制。

3、Any类型

泛型类型,可以存放任意类型的数据。

是google直接写好的,使用前需要

python 复制代码
import "google/protobuf/any.proto";

声明变量的格式如下:

cpp 复制代码
message PeopleInfo
{
	google.protobuf.Any data = 0;
}

使用any类型需要常用的接口:

cpp 复制代码
// .google.protobuf.Any data = 4;
bool has_data() const;//是否有数据
void clear_data();//清除数据
const ::PROTOBUF_NAMESPACE_ID::Any& data() const;//get
::PROTOBUF_NAMESPACE_ID::Any* mutable_data();//set
//把任意类型转化为any类型
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message);
//重载类型
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message,
              ::PROTOBUF_NAMESPACE_ID::ConstStringParam type_url_prefix);
//把any类型转化为任意类型
bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const;
//判断Any类型是否是T类型
template<typename T> bool Is() const {
    return _impl_._any_metadata_.Is<T>();
  }

4、oneof类型

被oneof声明的多个字段只能保留最后被设置的一个:

cpp 复制代码
oneof other_contact
{
	string qq = 5;
	string wechat = 6;
}

qq和微信字段只会保留最后一次被设置的值。

oneof内部不能使用repeated修饰字段。

常用方法包括clear,set,get。不同的是多了一个has方法,用来表明是否有这个方法。

在对应生成的Cpp代码中,oneof类型定义的多个类型会被转化为一个enum类型。内部除了oneof内部的类型之外,还有一个NOT_SET = 0常量值。

5、map类型

类似于C++的map,定义protobuf的map需要注意,key_type只能是float,bytes类型之外的标量类型。val_type可以是任意类型。
map不能被repeated修饰。
设置的key-val键值对是无序的。

常用方法:size,clear,get,set。


三、小工具

1.hexdump

hexdump是Linux下的⼀个二进制文件查看工具,它可以将二进制文件转换为ASCII、八进制、十进制、十六进制格式进行查看。
-C: 表示每个字节显示为16进制和相应的ASCII字符

这个工具意义在于把protobuf序列化后的二进制转化为ASCII,便于查看。

2.decode

ProtoBuf 提供的一个命令选项 --decode ,表表示从标准输入中读取给定类型的二进制消息,并将其以文本格式写入标准输出。 消息类型必须在 .proto 文件或导入的文件中定义。

bash 复制代码
protoc --decode=contacts.Contacts contacts.proto < contacts.bin

这条命令用于解码 一个用 Protocol Buffers(protobuf)序列化后的二进制文件 contacts.bin,并将其内容以可读的文本格式输出。下面是这条命令的详细解释:

  1. protoc

    这是 Protocol Buffers 的编译器命令行工具,用于编译 .proto 文件和处理 protobuf 数据。

  2. --decode=contacts.Contacts

    这个参数的意思是用 .proto 文件中定义的 contacts.Contacts 这个消息类型来解码输入的数据。

    • contacts 是 proto 文件中的包名(package)。
    • Contacts 是 proto 文件中定义的消息类型(message)。
  3. contacts.proto

    这是 protobuf 的协议文件,里面定义了数据结构(消息类型、包名等)。protoc 会用它来知道怎么解析二进制数据。

  4. < contacts.bin

    这表示将 contacts.bin 文件中的二进制数据(用 protobuf 序列化过的)作为标准输入传给 protoc


四、注意事项

  1. 字段编号(tag)不可轻易变更

    字段编号 是二进制格式的唯一标识,不能随意修改或复用,否则会导致兼容性问题。

  2. 尽量避免删除字段

    如果需要废弃字段,可用 reserved 关键字保留该 tag 和字段名。

  3. 字段类型不能随意更改

    特别是从一种类型改为不兼容的另一种类型(如 int32 改为 string),会导致解析错误。

  4. repeated 字段适合表达数组或列表

    表示 0 个或多个同类型数据项。例如 repeated int32 scores。

  5. 嵌套 message 和 enum

    可以在 message 内部定义子 message 或 enum,便于结构化复杂数据。

  6. 避免使用 float/double 存储精确金额

    由于浮点数精度问题,金额等精确数据建议用 int64/uint64 表示最小单位(如分、厘)。

  7. bytes 与 string 区别

    string 专为 UTF-8 文本,bytes 则可存储任意二进制数据,如图片、加密内容等。


完~
未经作者同意禁止转载


相关推荐
Sapphire~11 分钟前
Linux-07 ubuntu 的 chrome 启动不了
linux·chrome·ubuntu
伤不起bb15 分钟前
NoSQL 之 Redis 配置与优化
linux·运维·数据库·redis·nosql
Fanxt_Ja32 分钟前
【JVM】三色标记法原理
java·开发语言·jvm·算法
蓝婷儿33 分钟前
6个月Python学习计划 Day 15 - 函数式编程、高阶函数、生成器/迭代器
开发语言·python·学习
广东数字化转型35 分钟前
nginx怎么使用nginx-rtmp-module模块实现直播间功能
linux·运维·nginx
love530love41 分钟前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
啵啵学习43 分钟前
Linux 里 su 和 sudo 命令这两个有什么不一样?
linux·运维·服务器·单片机·ubuntu·centos·嵌入式
南郁1 小时前
007-nlohmann/json 项目应用-C++开源库108杰
c++·开源·json·nlohmann·现代c++·d2school·108杰
半桔1 小时前
【Linux手册】冯诺依曼体系结构
linux·缓存·职场和发展·系统架构
slandarer1 小时前
MATLAB | 绘图复刻(十九)| 轻松拿捏 Nature Communications 绘图
开发语言·matlab