Google第三方库详解------ProtoBuf详解 + 样例(5万字详解!)
目录
前言:
提示:
插件
入门:
ProtoBuf普通字段类型:
编译命令:
序列化与反序列化:
Proto3语法:
字段规则:数组类型
应用
将通讯录数据序列化后写入文件
工具介绍:hexdump
将文件数据反序列化到类中:
ProtoBuf命令选项:
Decode选项:
Proto3语法:
字段规则:enum类型
应用:
Proto3语法:
字段规则:Any类型:
any的相关接口介绍:
应用:
Proto3语法:
字段规则:oneof类型:
应用:
Proto3语法:
字段规则:map类型:
应用:
Proto3语法:
字段规则:默认值:
Proto3语法:
字段规则:更新消息:
字段规则:reserved:
Proto3语法:
字段规则:option:
前言:
提示:
安装等过程在此处就不阐述,请读者自行查阅:
代码演示环境:ubuntu22.04云服务器
插件
vscode支持ProtoBuf语法高亮的插件:vscode-proto
入门:
1.首先创建一个.proto文件
2.声明语法指定行
syntax = "proto3";
3.声明命名空间
package contacts;
4.定义一个message消息
message PeopleInfo
{string _name = 1;int32 _age = 2;
}
//其中,1和2都是ProtoBuf用来标识字段唯一性的ID,同一个message中id不能相同也不能缺省;
//ID范围 = {1,2^29-1],其中19000~19999之间的数不可用;
ProtoBuf普通字段类型:
//ProtoBuf类型
double
double
float
float
int32 :使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可能为负值,应使⽤ sint32 代替。
int64 :使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可能为负值,应使⽤ sint64 代替。
uint32:使⽤变⻓编码[1]。
uint64 :使⽤变⻓编码[1]。
sint32 :使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的 int32 类型。
sint64 :使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的 int64 类型。
fixed32: 定⻓ 4 字节。若值常⼤于2^28 则会⽐ uint32 更⾼效。
fixed64 :定⻓ 8 字节。若值常⼤于2^56 则会⽐ uint64 更⾼效。
sfixed32:定⻓ 4 字节。
sfixed64:定⻓ 8 字节。
bool类型
string:包含 UTF-8 和 ASCII 编码的字符串,⻓度不能超过 2^32 。
bytes:可包含任意的字节序列但⻓度不能超过 2^32 。
编译命令:
1.编译当前路径下的.protoc文件protoc --cpp_out=要保存的路径名 依赖的.proto文件例如:protoc --cpp_out=. contact.proto
2.编译指定路径下的.protoc文件protoc -I .protoc文件所处路径 --cpp_out=要保存的路径名 依赖的.proto文件
序列化与反序列化:
注意:ProtoBuf序列化后不是文本序列,而是二进制序列!
序列化接口:以Serialize开头
反序列化接口:以Parse开头
#include <iostream>
#include <string>
//#include"ProtoBuf编译后的头文件.h"
#include "contacts.pb.h"int main()
{std::string data;//序列化{contacts::PeopleInfo ple;ple.set_name("张三");ple.set_age(20);// 序列化if (!ple.SerializeToString(&data)){std::cout << "序列化联系人信息失败!" << std::endl;return -1;}std::cout << "Serialized str = " << data << std::endl;}//反序列化{contacts::PeopleInfo ple;if(!ple.ParseFromString(data)){std::cout<<"反序列化失败!"<<std::endl;return -2;}std::cout<<"反序列化成功!"<<std::endl<<"姓名: "<<ple.name()<<std::endl<<"年龄:"<<ple.age()<<std::endl;}return 0;
}
Makefile文件:
Main:Main.cpp contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:rm -f Main
Proto3语法:
字段规则:数组类型
1.数组类型:repeated
//样例1:
message PeopleInfo
{string name = 1;int32 age = 2;repeated string phone_numbers = 3;
}
//也可以是message类型的数组
message PhoneNumbers
{string phone = 1;
}
message PeopleInfo
{string name = 1;int32 age = 2;repeated PhoneNumbers phones = 3;
}
在一个.proto文件中使用另外一个.proto文件的message:
//以phone.proto和contacts.proto为例
//phone.proto
message PhoneNumbers
{string phone = 1;
}
//contacts.proto
syntax = "proto3";
package contacts;
import "phone.proto";message PeopleInfo
{string name = 1;int32 age = 2;repeated phone.PhoneNumbers phones = 3;
}
/*注意,如果外部的.proto文件中存在命名空间,则在本文件inport引入后,需要指定空间(空间名+.)
才能使用message*/
应用
将通讯录数据序列化后写入文件
接口介绍:以上述.proto文件编译后的文件为例:
1.add_contacts()
作用:返回一块新的contacts数组中的空间地址用以用户填充相关字段,作用类似于push_back
2.add_phones()
作用:返回一块新的contacts数组中phone数组中的空间地址用以用户填充相关字段,作用类似于push_back
3.cin.ignore()
作用:cin的ignore函数可以用来清空cin的缓冲区,传递一个足够大的参数和一个终止符即可,cin会根据传递的大小和终止符来清空包括终止符在内的缓冲区数据
code:
#include <iostream>
#include <fstream>
#include <string>
#include "contacts.pb.h"void AddContact(contacts::PeopleInfo *con)
{std::cout<<"---------------新增联系人---------------"<<std::endl;std::cout<<"输入联系人姓名: ";std::string name;std::getline(std::cin, name);con->set_name(name);int age = 0;std::cout<<"输入联系人年龄: ";std::cin >> age;con->set_age(age);// 清空cin缓冲区std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');// 新增电话for(int i = 1;true;i++){std::cout<<"---请输入该联系人的电话"<<i<<"(只输入空格表示完成)"<<": ";std::string number;std::getline(std::cin,number);if(number.size() == 0) break;contacts::PeopleInfo_Phone* phone = con->add_phones();phone->set_phone(number);}std::cout<<"---------------添加联系人成功---------------"<<std::endl;
}
int main()
{contacts::Contacts cons;std::ifstream in("contacts.bin", std::ios::binary);if (!in.is_open()){std::cout << "contacts.bin is not exist,create new contacts.bin" << std::endl;}else if (!cons.ParseFromIstream(&in)){std::cout << "Parse contact info error!" << std::endl;in.close();return -1;}AddContact(cons.add_contacts());// 序列化写入文件std::ofstream out("contacts.bin", std::ios::trunc | std::ios::binary);if (!out.is_open()){std::cout << "write file error!" << std::endl;return -2;}if (!cons.SerializePartialToOstream(&out)){std::cout << "序列化失败!" << std::endl;return -3;}in.close();out.close();return 0;
}
工具介绍:hexdump
将二进制数据转换为十六进制:
指令:hexdump -C 二进制文件
将文件数据反序列化到类中:
#include <iostream>
#include <fstream>
#include "contacts.pb.h"int main()
{contacts::Contacts cons;std::ifstream in("contacts.bin", std::ios::in | std::ios::binary);if (!in.is_open()){std::cout << "open contacts.bin error!" << std::endl;return -1;}else if (!cons.ParsePartialFromIstream(&in)){std::cout << "反序列化失败!" << std::endl;return -1;}for(int i = 0;i<cons.contacts_size();i++){std::cout<<"-------------------------------"<<std::endl;std::cout<<"------------联系人-------------"<<std::endl;auto people = cons.contacts(i);std::cout<<"姓名: ";std::cout<<people.name()<<std::endl;std::cout<<"年龄: "<<people.age()<<std::endl;std::cout<<"-----------联系电话-------------"<<std::endl;for(int i = 0;i<people.phones_size();i++){std::cout<<(people.phones(i)).phone()<<std::endl;}std::cout<<"----------- 联系人-------------"<<std::endl;std::cout<<"-----------------------------"<<std::endl;}in.close();return 0;
}
ProtoBuf命令选项:
查询ProtoBuf指令手册指令:protoc -h
Decode选项:
从标准输入流中将二进制数据转换成文本类型数据,可以将stdin重定向到某个二进制类型文件:
protoc --decode=命名空间.指定的message类型的变量名 指定的.proto文件 < 指定的二进制文件
Proto3语法:
字段规则:enum类型
枚举类型中,第一个枚举的变量值一定要以0开头,枚举类型中的变量不能设置为负值,且只能是32位数字:
注意:1.同一个文件中的枚举类型中的变量名不能相同;
2.在多个.proto文件中,如果枚举类型名字相同,需要申明一下package
3.import一个.proto文件后,如果不适用其中的任何东西,编译时会有warning
syntax = "proto3";
package contacts;message PeopleInfo
{string name = 1;int32 age = 2;message Phone {string number = 1;enum PhoneType{HP = 0;TEL = 1;}PhoneType type = 2;}repeated Phone phones = 3;
}message Contacts
{repeated PeopleInfo contacts = 1;
}
应用:
接口介绍:1.set_type(type)作用:设置类型,在上述例子中参数为:contacts::PeopleInfo_Phone_PhoneType中的常量2.PhoneType_Name(type)作用:将传递的参数type转换成字符串返回3.Phones(int index)作用:返回数组中index下标对应的元素
//write.cc
#include <iostream>
#include <fstream>
#include <string>
#include "contacts.pb.h"void AddContact(contacts::PeopleInfo *con)
{std::cout<<"---------------新增联系人---------------"<<std::endl;std::cout<<"输入联系人姓名: ";std::string name;std::getline(std::cin, name);con->set_name(name);int age = 0;std::cout<<"输入联系人年龄: ";std::cin >> age;con->set_age(age);// 清空cin缓冲区std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');// 新增电话for(int i = 1;true;i++){std::cout<<"---请输入该联系人的电话"<<i<<"(只输入空格表示完成)"<<": ";std::string number;std::getline(std::cin,number);if(number.size() == 0) break;contacts::PeopleInfo_Phone* phone = con->add_phones();// phone->set_phone(number);phone->set_number(number);std::cout<<"--请输入该联系人电话类型:|1.HP--2.TEL|-----: ";int type;std::cin>>type;switch(type){case 1: phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_HP);break;case 2:phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);break;}}std::cout<<"---------------添加联系人成功---------------"<<std::endl;
}
int main()
{contacts::Contacts cons;std::ifstream in("contacts.bin", std::ios::binary);if (!in.is_open()){std::cout << "contacts.bin is not exist,create new contacts.bin" << std::endl;}else if (!cons.ParseFromIstream(&in)){std::cout << "Parse contact info error!" << std::endl;in.close();return -1;}AddContact(cons.add_contacts());// 序列化写入文件std::ofstream out("contacts.bin", std::ios::trunc | std::ios::binary);if (!out.is_open()){std::cout << "write file error!" << std::endl;return -2;}if (!cons.SerializePartialToOstream(&out)){std::cout << "序列化失败!" << std::endl;return -3;}in.close();out.close();return 0;
}
//read.cc
#include <iostream>
#include <fstream>
#include "contacts.pb.h"int main()
{contacts::Contacts cons;std::ifstream in("contacts.bin", std::ios::in | std::ios::binary);if (!in.is_open()){std::cout << "open contacts.bin error!" << std::endl;return -1;}else if (!cons.ParsePartialFromIstream(&in)){std::cout << "反序列化失败!" << std::endl;return -1;}for(int i = 0;i<cons.contacts_size();i++){std::cout<<"-------------------------------"<<std::endl;std::cout<<"------------联系人-------------"<<std::endl;auto people = cons.contacts(i);std::cout<<"姓名: ";std::cout<<people.name()<<std::endl;std::cout<<"年龄: "<<people.age()<<std::endl;std::cout<<"-----------联系电话-------------"<<std::endl;for(int i = 0;i<people.phones_size();i++){contacts::PeopleInfo_Phone phone = people.phones(i);std::cout<<phone.number()<<std::endl;std::cout<<"电话类型: "<<phone.PhoneType_Name(phone.type())<<std::endl;}std::cout<<"----------- 联系人-------------"<<std::endl;std::cout<<"-----------------------------"<<std::endl;}in.close();return 0;
}
Proto3语法:
字段规则:Any类型:
首先,先查看ProtoBuf库的路径位置:
/usr/local/include/google/protobuf/
在这个路径下存在一个any.ptoto文件
//1,引入any.proto
import "google/protobuf/any.protoc";
syntax = "proto3";
package contacts;import "google/protobuf/any.proto";//地址信息
message Address{string util_address = 1;string home_address = 2;
}message PeopleInfo{string name = 1;int32 age = 2;message Phone {string number = 1;enum PhoneType{MP = 0;TEL = 1;}PhoneType type = 2;}repeated Phone phones = 3;google.protobuf.Any data = 4;
}message Contacts{repeated PeopleInfo contacts = 1;
}
any的相关接口介绍:
any在google::protobuf::Any::中,即定义:google::protobuf::Any::变量名
1.mutablek开头的接口()
作用:返回一个开辟好的any空间
在上述.proto文件编译的前提下:接口名为:mutablek_data();
2.PackFrom(message&)
作用:将一个message类型的对象存储到any对象中
3.UnpackTo(message*)
作用:将一个any对象中存储的message对象提取出来;
4.has_data()
作用:判断内部的any对象是是否有数据,有则返回true.反之false
应用:
//为联系人信息增加地址信息
//write.cc
#include <iostream>
#include <fstream>
#include <string>
#include "contacts.pb.h"void AddContact(contacts::PeopleInfo *con)
{std::cout<<"---------------新增联系人---------------"<<std::endl;std::cout<<"输入联系人姓名: ";std::string name;std::getline(std::cin, name);con->set_name(name);int age = 0;std::cout<<"输入联系人年龄: ";std::cin >> age;con->set_age(age);// 清空cin缓冲区std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');// 新增电话for(int i = 1;true;i++){std::cout<<"---请输入该联系人的电话"<<i<<"(只输入空格表示完成)"<<": ";std::string number;std::getline(std::cin,number);if(number.size() == 0) break;contacts::PeopleInfo_Phone* phone = con->add_phones();// phone->set_phone(number);phone->set_number(number);std::cout<<"--请输入该联系人电话类型:|1.移动电话--2.固定电话|-----: ";int type;std::cin>>type;std::cin.ignore(256,'\n');switch(type){case 1: phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);break;case 2:phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);break;}//填充地址信息contacts::Address address;std::cout<<"请输入工作单位地址(不填请按空格结束): ";std::string util_address;std::getline(std::cin,util_address);address.set_util_address(util_address);std::string home_address;std::cout<<"请输入家庭地址(不填请按空格结束): ";std::getline(std::cin,home_address);address.set_home_address(home_address);//people中的mutable_data方法会返回一个开辟好的any对象地址google::protobuf::Any* any = con->mutable_data();//将message信息解析到any中any->PackFrom(address);}std::cout<<"---------------添加联系人成功---------------"<<std::endl;
}
int main()
{contacts::Contacts cons;std::ifstream in("contacts.bin", std::ios::binary);if (!in.is_open()){std::cout << "contacts.bin is not exist,create new contacts.bin" << std::endl;}else if (!cons.ParseFromIstream(&in)){std::cout << "Parse contact info error!" << std::endl;in.close();return -1;}AddContact(cons.add_contacts());// 序列化写入文件std::ofstream out("contacts.bin", std::ios::trunc | std::ios::binary);if (!out.is_open()){std::cout << "write file error!" << std::endl;return -2;}if (!cons.SerializePartialToOstream(&out)){std::cout << "序列化失败!" << std::endl;return -3;}in.close();out.close();return 0;
}
//read.cc
#include <iostream>
#include <fstream>
#include "contacts.pb.h"int main()
{contacts::Contacts cons;std::ifstream in("contacts.bin", std::ios::in | std::ios::binary);if (!in.is_open()){std::cout << "open contacts.bin error!" << std::endl;return -1;}else if (!cons.ParsePartialFromIstream(&in)){std::cout << "反序列化失败!" << std::endl;return -1;}for (int i = 0; i < cons.contacts_size(); i++){std::cout << "------------联系人--------------" << std::endl;std::cout << "-------------------------------" << std::endl;auto people = cons.contacts(i);std::cout << "姓名: ";std::cout << people.name() << std::endl;std::cout << "年龄: " << people.age() << std::endl;std::cout << "-----------联系电话-------------" << std::endl;for (int i = 0; i < people.phones_size(); i++){contacts::PeopleInfo_Phone phone = people.phones(i);std::cout << phone.number() << std::endl;std::cout << "电话类型: " << phone.PhoneType_Name(phone.type()) << std::endl;}if (people.has_data()){std::cout << "------------地址----------------" << std::endl;google::protobuf::Any any = people.data();contacts::Address address;any.UnpackTo(&address);std::cout << "工作单位地址: " << address.util_address() << std::endl;std::cout << "家庭地址: " << address.home_address() << std::endl;std::cout << "--------------------------------" << std::endl;std::cout << "----------- 联系人--------------" << std::endl;}else{std::cout << "any is not setting!" << std::endl;}}in.close();return 0;
}
Proto3语法:
字段规则:oneof类型:
oneof类型是多选一的
//contacts.proto
例如用来定义:oneof other_contact{string qq = 5;string wechat = 6;}
//contacts。proto文件
syntax = "proto3";
package contacts;import "google/protobuf/any.proto";//地址信息
message Address{string util_address = 1;string home_address = 2;
}message PeopleInfo{string name = 1;int32 age = 2;message Phone {string number = 1;enum PhoneType{MP = 0;TEL = 1;}PhoneType type = 2;}repeated Phone phones = 3;google.protobuf.Any data = 4;//其他联系方式oneof other_contact{string qq = 5;string wechat = 6;}
}message Contacts{repeated PeopleInfo contacts = 1;
}
应用:
接口解释:
1.通过oneof设置的字段,只能填写一个,即多选一模式,以最后一次填写的字段为准
2.other_contact_case()
作用:返回内部被设置的那个字段的常量。类型为contacts::PeopleInfo::OtherContactCase
用于读取文件时判断哪个字段是有效的。
//write.ccstd::cout << "请输入其他联系方式->|1.qq---2.微信|: ";int op = 0;std::cin >> op;std::cin.ignore(256, '\n');std::string other_contact;switch (op){case 1:std::cout << "请输入qq号: ";std::getline(std::cin, other_contact);con->set_qq(other_contact);break;case 2:std::cout << "请输入微信号: ";std::getline(std::cin, other_contact);con->set_wechat(other_contact);break;default:std::cout<<"未知错误!"<<std::endl;break;}
//Read.cc// 查看其他联系方式contacts::PeopleInfo::OtherContactCase other_con = people.other_contact_case();switch (other_con){case contacts::PeopleInfo::OtherContactCase::kQq:std::cout << "QQ: " << people.qq() << std::endl;break;case contacts::PeopleInfo::OtherContactCase::kWechat:std::cout << "微信: " << people.wechat() << std::endl;break;default:std::cout << "未知错误!" << std::endl;break;}
Proto3语法:
字段规则:map类型:
1.map字段不能被repeated修饰//.proto文件
syntax = "proto3";
package contacts;import "google/protobuf/any.proto";//地址信息
message Address{string util_address = 1;string home_address = 2;
}message PeopleInfo{string name = 1;int32 age = 2;message Phone {string number = 1;enum PhoneType{MP = 0;TEL = 1;}PhoneType type = 2;}repeated Phone phones = 3;google.protobuf.Any data = 4;//其他联系方式oneof other_contact{string qq = 5;string wechat = 6;}//备注信息map<string,string> remarks = 7;
}message Contacts{repeated PeopleInfo contacts = 1;
}
应用:
接口介绍:
1.mutable_remarks()
作用:返回一块开辟好的map空间地址
2.insert()
作用:新增map类型的数据--即key-val键值对
注意:不能使用make_pair,可以使用{key,val}插入
3.remarks()
作用:获取map对象
4.begin()和cbegin()
作用:迭代器,用法和c++的map一致
//write.ccstd::cout<<"请输入该联系人的备注信息: ";google::protobuf::Map<std::string, std::string>* remark = con->mutable_remarks();for(int i = 1;;i++){std::cout<<"请输入备注标题"<<i<<"(按下空格结束): ";std::string key;std::getline(std::cin,key);if(key.size() == 0) break;std::cout<<"请输入备注内容"<<"(按下空格结束): ";std::string val;std::getline(std::cin,val);if(val.size() == 0) break;remark->insert({key,val});}
//Read.cc
std::cout<<"---------查看备注信息-----------"<<std::endl;
auto remark = people.remarks();
for(int i = 0;i<people.remarks_size();i++)
{std::cout<<"备注标题"<<i+1<<": "<< ((remark.begin())->first)<<std::endl;std::cout<<"备注内容: "<<((remark.begin())->second)<<std::endl;
}
std::cout<<"----------------------------------"<<std::endl;
Proto3语法:
字段规则:默认值:
//默认值
1.反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就
会设置为该字段的默认值。不同的类型对应的默认值不同:
2.对于字符串,默认值为空字符串。
3.对于字节,默认值为空字节。
4.对于布尔值,默认值为 false。
5.对于数值类型,默认值为 0。
6.对于枚举,默认值是第⼀个定义的枚举值, 必须为 0。
7.对于消息字段,未设置该字段。它的取值是依赖于语⾔。
8.对于设置了 repeated 的字段的默认值是空的( 通常是相应语⾔的⼀个空列表 )。
9.对于 消息字段 、 oneof字段 和 any字段 ,C++ 和 Java 语⾔中都有 has_ ⽅法来检测当前字段
是否被设置。
注意:proto3语法下,标量数据类型没有生成has_方法,保证对于标量字段用户一定要手动初始化!
Proto3语法:
字段规则:更新消息:
1.如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情
况下更新消息类型⾮常简单。遵循如下规则即可:
2.禁⽌修改任何已有字段的字段编号。
3.若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号
(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。
4.int32, uint32, int64, uint64 和 bool 是完全兼容的。可以从这些类型中的⼀个改为另⼀个,
⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与 C++ ⼀致的处理⽅案
(例如,若将 64 位整数当做 32 位进⾏读取,它将被截断为 32 位)。
5.sint32 和 sint64 相互兼容但不与其他的整型兼容。
6.string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。
7.bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。
8.fixed32 与 sfixed32 兼容, fixed64 与 sfixed64兼容。
9.enum 与 int32,uint32, int64 和 uint64 兼容(注意若值不匹配会被截断)。但要注意当反序
列化消息时会根据语⾔采⽤不同的处理⽅案:例如,未识别的 proto3 枚举类型会被保存在消息
中,但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。
10.oneof:将⼀个单独的值更改为 新 oneof 类型成员之⼀是安全和⼆进制兼容的。
11.若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可⾏的。
12.将任何字段移⼊已存在的 oneof 类型是不安全的。
字段规则:reserved:
1.reserved修饰的编号不能被使用!
如果要删除一个已有的消息字段,不要使用其对应的字段编号,用reserved修饰即可
//例子:reserved 1;int age = 1;
此时编译不被允许!
2.reserved可以用来修饰字段名
//例子:reserved "age";int32 age = 2;//要删除的字段此时编译不被允许!
3.reserved可以用来修饰大量数字
写法1:reserved 1,2,3,...;
写法2:reserved 100 to 200;
连续修饰字段名可以用逗号分隔开
Proto3语法:
字段规则:option:
本质是影响protobuf的编译功能1.选项1:SPEEDZ:速度最快,但是占用空间更大
2.选项2:CODE_SIZE:速度最慢,空间更小,适用于包含大量的.proto文件中使用
3.选项3:LITE_RUNTIME:阉割版protobuf,仅仅提供序列化和反序列化功能+encode
4.选项4:allow_alias:允许将相同的常量值赋值给相同的枚举常量值
option allow_alias = true;
done
相关文章:
Google第三方库详解------ProtoBuf详解 + 样例(5万字详解!)
目录 前言: 提示: 插件 入门: ProtoBuf普通字段类型: 编译命令: 序列化与反序列化: Proto3语法: 字段规则:数组类型 应用 将通讯录数据序列化后写入文件 工具介绍&#x…...
机器学习数学通关指南——泰勒公式
前言 本文隶属于专栏《机器学习数学通关指南》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见《机器学习数学通关指南》 正文 一句话总结 泰勒公式是用多…...
如果后台的Long类型的数据返回是null,那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗?
如果后台的Long类型的数据返回是null,那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗? DeepSeek R1 思考 35 秒 思考过程 好的,用户的问题是关于在Android客户端使用Kotlin处理后台返回的Long类型数据为n…...

ai-financial-agent - 为金融投资打造的AI代理
探索人工智能在投资研究中的应用。本项目仅用于**教育**目的,不用于真实交易或投资。 作者声明: 本项目仅用于教育和研究目的。 不用于真实交易或投资不提供任何保证或担保过去的表现并不代表未来的结果Creator 对经济损失不承担任何责任咨询财务顾问…...

学习路程三 数据加载及向量化
前序 之前简单粗暴将LangChain分了几块,现在就挨着了解学习每块内容。今天主要从文档这条路来看。 本地文档这一条链路,通过加载,分割,向量化,再存储数据库 ps:看到这里还想继续实操下去,可以…...

基于GWO灰狼优化的WSN网络最优节点部署算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 无线传感器网络(Wireless Sensor Network, WSN)由大量分布式传感器节点组成,用于监测物理或环境状况。节点部署是 WSN 的关键问…...

保姆级! 本地部署DeepSeek-R1大模型 安装Ollama Api 后,Postman本地调用 deepseek
要在Postman中访问Ollama API并调用DeepSeek模型,你需要遵循以下步骤。首先,确保你有一个有效的Ollama服务器实例运行中,并且DeepSeek模型已经被加载。 可以参考我的这篇博客 保姆级!使用Ollama本地部署DeepSeek-R1大模型 并java…...
架构对比分析
您提到的两种架构描述本质上遵循相同的分层设计理念,但存在差异的原因在于 视角不同 和 硬件平台特性。以下是详细解析: 一、架构对比分析 1. 逻辑分层(通用软件设计视角) 应用层(UI/用户交互)↓ 业务逻辑…...

【每日八股】Redis篇(二):数据结构
Redis 数据类型? 主要有 STRING、LIST、ZSET、SET 和 HASH。 STRING String 类型底层的数据结构实现主要是 SDS(简单动态字符串),其主要应用场景包括: 缓存对象:可以用 STRING 缓存整个对象的 JSON&…...

windows使用命令解压jar包,替换里面的文件。并重新打包成jar包,解决Failed to get nested archive for entry
有一个jar包,需要替换里面的文件,使用解压工具打开项目,然后找到对应的子包,再次打开,然后进行手工替换重新压缩成jar包后,发现启动服务报错Failed to get nested archive for entry。 使用下面的命令可实…...
2025电商与跨境贸易实战全解析:DeepSeek赋能细分领域深度指南(附全流程案例)
🚀 2025电商与跨境贸易实战全解析:DeepSeek赋能细分领域深度指南(附全流程案例)🚀 📚 目录 DeepSeek在电商与跨境贸易中的核心价值选品与市场分析:AI驱动的精准决策Listing优化与多语言营销:提升转化率的秘密物流与供应链管理:AI赋能的效率革命客户服务与私域运营:…...

驱动开发系列39 - Linux Graphics 3D 绘制流程(二)- 设置渲染管线
一:概述 Intel 的 Iris 驱动是 Mesa 中的 Gallium 驱动,主要用于 Intel Gen8+ GPU(Broadwell 及更新架构)。它负责与 i915 内核 DRM 驱动交互,并通过 Vulkan(ANV)、OpenGL(Iris Gallium)、或 OpenCL(Clover)来提供 3D 加速。在 Iris 驱动中,GPU Pipeline 设置 涉及…...
自动驾驶中planning为什么要把横纵向分开优化?
在自动驾驶系统中,将 横向(Lateral)规划 和 纵向(Longitudinal)规划 分开优化是一种常见的设计范式,其核心原理在于 解耦车辆运动控制的多维复杂性,同时兼顾 计算效率 和 安全性约束。以下从原理…...
Linux 命令大全完整版(06)
2. 系统设置命令 pwunconv 功能说明:关闭用户的投影密码。语法:pwunconv补充说明:执行 pwunconv 指令可以关闭用户投影密码,它会把密码从 shadow 文件内,重回存到 passwd 文件里。 rdate(receive date) 功能说明&a…...
第9章:LangChain结构化输出-示例2(数字提取服务)
如何使用LangChain4j框架创建和使用多种AI服务。它通过定义接口和注解,将自然语言处理任务(如情感分析、数字提取、日期提取、POJO提取等)封装为服务,并通过LangChain4j的AiServices动态生成这些服务的实现。 本章主要讲述基于Lan…...

每天五分钟深度学习pytorch:使用Inception模块搭建GoogLeNet模型
本文重点 前面我们学习了Incetption模块,它的作用类似于vgg块对于VGG网络模型一样,本文我们使用Inception搭建GoogLeNet网络,如果使用卷积层开始从头开始搭建GoogleNet,那么这样看起来会很不清晰,我们使用已经封装好的Inception来搭建GoogLeNet网络 关键点 关键点在于I…...
Ubuntu - Redis 安装、远程访问
参考教程: https://blog.csdn.net/houor/article/details/126672577 https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/ 查看是否安装 redis-cli --versionUbuntu 上安装 更新: sudo apt update …...

SpringBoot+Vue+微信小程序的猫咖小程序平台(程序+论文+讲解+安装+调试+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 在当下这个高速发展的时代,网络科技正以令人惊叹的速度不断迭代更新。从 5G …...
二分查找算法的全面解析C++
一、核心原理与特性 二分查找是一种**对数时间复杂度(O(log n))**的高效搜索算法46,需满足两个前提条件: 数据存储在连续内存空间(如数组)数据按升序/降序有序排列35 算法通过折半比较缩小搜索范围: 初始化左右边界…...

深度学习(5)-卷积神经网络
我们将深入理解卷积神经网络的原理,以及它为什么在计算机视觉任务上如此成功。我们先来看一个简单的卷积神经网络示例,它用干对 MNIST数字进行分类。这个任务在第2章用密集连接网络做过,当时的测试精度约为 97.8%。虽然这个卷积神经网络很简单…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...