文章目录
- 为何选择PB
- PB安装
- Windows
- Mac未完待续
- 语法
- 命令行编译
- Maven插件编译
- UDP通信的例子
- 3大序列化方法对比
为何选择PB
在网络传输和存储数据的时候比传统的 JSON 效果更好
PB安装
GitHub
Windows
- 下载
- 配置环境变量
- 验证
Mac未完待续
后续补充Mac安装方式
语法
使用过程很简单,并没有太多的语法关注点。我们只需要掌握重要的一些方法即可
创建的 .proto 文件一定要全部小写,中间用 “_” 链接【lower_snake_case.proto】
// 字段规则,说明是 proto3 语法
syntax = "proto3";
// 包名
package contact;
// 开启多文件
option java_multiple_files = true;
// 生成的 .java 文件的包路径
option java_package = "entityPB";
// 生成的 .proto 包装类的 .java 文件的类名
option java_outer_classname = "ContactsProto";
// 需要用到 any 数据类型就需要导入包【protoc-23.1-win64\include\google\protobuf】
import "google/protobuf/any.proto";
// message 消息类型命名规范:使用驼峰命名,首字母大写
message PeopleInfoPB{
/*
字段定义格式为:字段类型 字段名 = 字段唯⼀编号
字段名称命名规范:全小写字⺟,多个字⺟之间⽤ _ 连接。
字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。
字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。
*/
string name = 1;// 姓名
int32 age = 2;// 年龄
message Phone{
string number = 1;// 电话号码
/*
枚举
同一文件下枚举类型不能出现相同的字段
*/
enum PhoneType {
MP = 0;// 移动电话
TEL = 1;// 固定电话
}
PhoneType type = 2;// 电话类型
}
// repeated 说明 phone 这个数据是一个可重复的,也就是数组的形式
repeated Phone phone = 3;
/*
oneof:如果消息中有很多字段,但是只会用到一个字段。后续设置的 wechat 会将 QQ 清空
*/
oneof other_contact{// 其它联系方式
string qq = 4;
string wechat = 5;
}
/*
any 类型可以理解为泛型类型
可以用 repeated 修饰
*/
google.protobuf.Any data = 6;// 存放联系地址
/*
map 不能用 repeated 修饰
key:除了 float,bytes 以外任意的类型
value:任意类型
*/
map<string, string> remark = 7;// 备注
}
message Address{
string home_address = 1;// 家庭地址
string unit_address = 2;// 公司地址
}
message Contacts {
repeated PeopleInfoPB contacts = 1;// 通讯录
}
.protoType标量类型 | Notes | java |
---|---|---|
int32 | 使⽤变⻓编码[1]。负数的编码效率较低,若字段可能为负值,应使⽤ sint32 代替 | int |
int64 | 使⽤变⻓编码[1]。负数的编码效率较低,若字段可能为负值,应使⽤ sint64 代替 | int |
uint32 | 使⽤变⻓编码[1] | int |
uint64 | 使⽤变⻓编码[1] | int |
sint32 | 使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的 int32 类型 | int |
sint64 | 使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的 int64 类型 | long |
fixed32 | 定⻓ 4 字节。若值常⼤于2^28 则会⽐ uint32 更⾼效 | int |
fixed64 | 定⻓ 8 字节。若值常⼤于2^56 则会⽐ uint64 更⾼效 | long |
sfixed32 | 定⻓ 4 字节 | int |
sfixed64 | 定⻓ 8 字节 | long |
float | float | |
double | double | |
bytes | 可包含任意的字节序列但⻓度不能超过 2^32 | ByteString |
bool | boolean | |
string | 包含 UTF-8 和 ASCII 编码的字符串,⻓度不能超过2^32 | string |
类型 | 值 |
---|---|
string | 空字符串 |
bytes | 空字节 |
bool | false |
int | 0 |
enum枚举 | 默认值是第一个枚举值,必须为0 |
消息字段 | 未设置该字段,默认值依赖于具体的语言 |
repeated修饰 | 默认是一个列表【addXXX方法】 |
对于消息字段,oneof字段和any字段 | 都有has方法来检测当前字段是否被设置值 |
命令行编译
# protoc -I 搜索路径 --java_out=输出的java文件路径 .proto文件
protoc -I BasicGrammar/src/main/proto/start --java_out=BasicGrammar/src/main/java contacts.proto
这种每次不方便,因此衍生出插件编译
Maven插件编译
要选择版本对应的。我的 protocol buff 是 3.23.1,那么插件版本对应的也应该是 3.23.1
maven-repository仓库
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java 版本对应【23.1】-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.23.1</version>
</dependency>
<plugin>
<!-- https://mvnrepository.com/artifact/org.xolstice.maven.plugins/protobuf-maven-plugin -->
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!--本地安装的protoc.exe运行路径-->
<protocExecutable>D:\Documents\Tools\protoc-23.1-win64\bin\protoc.exe</protocExecutable>
<!--protoc文件放置的目录,默认为/src/main/proto-->
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<!--生成文件的目录,默认生成到target/generated-sources/protobuf/下-->
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<!--是否清空目标目录,默认值为true。这个最好设置为false,以免误删项目文件-->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
</plugin>
每次双击插件即可完成编译指令
UDP通信的例子
网络数据传输中需要对数据进行序列化和反序列化
.客户端protp文件代码
syntax = "proto3";
package client_internet;
option java_multiple_files = true;
option java_package = "internet.client";
option java_outer_classname = "ContactsProtos";
message Request{
string name = 1;
int32 age = 2;
message Phone{
string number = 1;
}
repeated Phone phone = 3;
}
message Response{
string uid = 1;
}
客户端Java代码
package internet.client;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class ContactsClient {
private static void clientStart(String ip, int port) throws IOException {
// 1.创建客户端 socket
DatagramSocket socket = new DatagramSocket();
// 2.序列化发送给服务端的数据
Request req = Request.newBuilder().setName("zhang san").setAge(20).addPhone(Request.Phone.newBuilder().setNumber("177-9824-4450")).build();
byte[] reqData = req.toByteArray();
// 3.给服务端发送数据
DatagramPacket reqPacket = new DatagramPacket(reqData, reqData.length, InetAddress.getByName(ip), port);
socket.send(reqPacket);
// 4.接收服务端数据
DatagramPacket respPacket = new DatagramPacket(new byte[8192], 8192);
socket.receive(respPacket);
// 5.反序列化接收到的服务端数据
int len = respPacket.getLength();
byte[] respData = respPacket.getData();
byte[] respNewData = new byte[len];
System.arraycopy(respData, 0, respNewData, 0, len);
Response response = Response.parseFrom(respNewData);
System.out.printf("接收服务端数据:" + response.toString());
socket.close();
}
public static void main(String[] args) throws IOException {
String ip = "127.0.0.1";
int port = 9090;
clientStart(ip, port);
}
}
服务端 .proto 代码
syntax = "proto3";
package service_internet;
option java_multiple_files = true;
option java_package = "internet.service";
option java_outer_classname = "ContactsProtos";
message Request{
string name = 1;
int32 age = 2;
message Phone{
string number = 1;
}
repeated Phone phone = 3;
}
message Response{
string uid = 1;
}
服务端代码
package internet.service;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
// 服务端反序列化
public class ContactsService {
private static void startService(int port) throws IOException {
// 1.创建服务端 socket
DatagramSocket socket = new DatagramSocket(port);
while (true) {
System.out.println("等待客户端发送数据...");
// 2.接收客户端数据
DatagramPacket reqPacket = new DatagramPacket(new byte[8192], 8192);
socket.receive(reqPacket);
// 3.反序列化接收到的客户端数据
byte[] reqData = reqPacket.getData();
int len = reqPacket.getLength();
byte[] reqNewData = new byte[len];
System.arraycopy(reqData, 0, reqNewData, 0, len);
Request request = Request.parseFrom(reqNewData);
// System.out.println("接收客户端端数据:" + new String(request.toByteArray(), StandardCharsets.UTF_8));
System.out.println("接收客户端端数据:" + request.toString());
// 4.序列化发送给客户端的数据
Response response = Response.newBuilder().setUid("200").build();
byte[] respData = response.toByteArray();
// 5.给客户端发送数据
DatagramPacket respPacket = new DatagramPacket(respData, 0, respData.length, reqPacket.getSocketAddress());
socket.send(respPacket);
}
}
public static void main(String[] args) throws IOException {
int port = 9090;
startService(port);
}
}
3大序列化方法对比
PB.proto 代码
// 字段规则,说明是 proto3 语法
syntax = "proto3";
// 包名
package contact;
// 开启多文件
option java_multiple_files = true;
// 生成的 .java 文件的包路径
option java_package = "entityPB";
// 生成的 .proto 包装类的 .java 文件的类名
option java_outer_classname = "ContactsProto";
// 需要用到 any 数据类型就需要导入包【protoc-23.1-win64\include\google\protobuf】
import "google/protobuf/any.proto";
// message 消息类型命名规范:使用驼峰命名,首字母大写
message PeopleInfoPB{
/*
字段定义格式为:字段类型 字段名 = 字段唯⼀编号
字段名称命名规范:全小写字⺟,多个字⺟之间⽤ _ 连接。
字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。
字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。
*/
string name = 1;// 姓名
int32 age = 2;// 年龄
message Phone{
string number = 1;// 电话号码
/*
枚举
同一文件下枚举类型不能出现相同的字段
*/
enum PhoneType {
MP = 0;// 移动电话
TEL = 1;// 固定电话
}
PhoneType type = 2;// 电话类型
}
// repeated 说明 phone 这个数据是一个可重复的,也就是数组的形式
repeated Phone phone = 3;
/*
oneof:如果消息中有很多字段,但是只会用到一个字段。后续设置的 wechat 会将 QQ 清空
*/
oneof other_contact{// 其它联系方式
string qq = 4;
string wechat = 5;
}
/*
any 类型可以理解为泛型类型
可以用 repeated 修饰
*/
google.protobuf.Any data = 6;// 存放联系地址
/*
map 不能用 repeated 修饰
key:除了 float,bytes 以外任意的类型
value:任意类型
*/
map<string, string> remark = 7;// 备注
}
message Address{
string home_address = 1;// 家庭地址
string unit_address = 2;// 公司地址
}
message Contacts {
repeated PeopleInfoPB contacts = 1;// 通讯录
}
普通的Java实体类
package entity;
import java.util.HashMap;
import java.util.List;
public class PeopleInfoEntity {
private String name;
private int age;
private String qq;
public static class Phone {
private String number;
public static enum PhoneType {
MP, TEL;
}
PhoneType type;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public PhoneType getType() {
return type;
}
public void setType(PhoneType type) {
this.type = type;
}
@Override
public String toString() {
return "Phone{" +
"number='" + number + '\'' +
", type=" + type +
'}';
}
}
private List<Phone> phones;
public static class Address {
private String home_address;
private String unit_address;
public String getHome_address() {
return home_address;
}
public void setHome_address(String home_address) {
this.home_address = home_address;
}
public String getUnit_address() {
return unit_address;
}
public void setUnit_address(String unit_address) {
this.unit_address = unit_address;
}
@Override
public String toString() {
return "Address{" +
"home_address='" + home_address + '\'' +
", unit_address='" + unit_address + '\'' +
'}';
}
}
private Address address;
private HashMap<String, String> remark;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getQq() {
return qq;
}
public void setQq(String qq) {
this.qq = qq;
}
public List<Phone> getPhones() {
return phones;
}
public void setPhones(List<Phone> phones) {
this.phones = phones;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public HashMap<String, String> getRemark() {
return remark;
}
public void setRemark(HashMap<String, String> remark) {
this.remark = remark;
}
@Override
public String toString() {
return "PeopleInfoEntity{" +
"name='" + name + '\'' +
", age=" + age +
", qq='" + qq + '\'' +
", phones=" + phones +
", address=" + address +
", remark=" + remark +
'}';
}
}
效率对比测试代码
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import entity.PeopleInfoEntity;
import entityPB.Address;
import entityPB.PeopleInfoPB;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class TestJSON {
private static PeopleInfoEntity createEntity() {
PeopleInfoEntity peopleInfo = new PeopleInfoEntity();
peopleInfo.setName("张三");
peopleInfo.setAge(24);
// 添加电话
List<PeopleInfoEntity.Phone> phones = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
PeopleInfoEntity.Phone phone = new PeopleInfoEntity.Phone();
phone.setNumber("193-0719-3096");
phone.setType(PeopleInfoEntity.Phone.PhoneType.MP);
phones.add(phone);
}
peopleInfo.setPhones(phones);
peopleInfo.setQq("1969-612859");
PeopleInfoEntity.Address address = new PeopleInfoEntity.Address();
address.setHome_address("湖北省十堰市");
address.setUnit_address("湖北省武汉市");
peopleInfo.setAddress(address);
HashMap<String, String> remark = new HashMap<String, String>() {{
put("key1", "value1");
put("key2", "value2");
put("key3", "value3");
put("key4", "value4");
put("key5", "value5");
put("key6", "value6");
}};
peopleInfo.setRemark(remark);
return peopleInfo;
}
private static PeopleInfoPB createEntityPB() {
PeopleInfoPB.Builder peopelInfoBuilder = PeopleInfoPB.newBuilder();
peopelInfoBuilder.setName("张三");
peopelInfoBuilder.setAge(24);
// 设置 repeated+enum 数据类型
for (int i = 1; i <= 5; i++) {
PeopleInfoPB.Phone.Builder phoneBuilder = PeopleInfoPB.Phone.newBuilder();
phoneBuilder.setNumber("193-0719-3096");
phoneBuilder.setType(PeopleInfoPB.Phone.PhoneType.MP);
peopelInfoBuilder.addPhone(phoneBuilder);
}
// 设置 oneof 数据类型
peopelInfoBuilder.setQq("1969-612859");
// 设置 Any 数据类型
Address.Builder addressBuilder = Address.newBuilder();
addressBuilder.setHomeAddress("湖北省十堰市");
addressBuilder.setUnitAddress("湖北省武汉市");
peopelInfoBuilder.setData(Any.pack(addressBuilder.build()));
// 设置 map 备注
peopelInfoBuilder.putRemark("key1", "value1");
peopelInfoBuilder.putRemark("key2", "value2");
peopelInfoBuilder.putRemark("key3", "value3");
peopelInfoBuilder.putRemark("key4", "value4");
peopelInfoBuilder.putRemark("key5", "value5");
peopelInfoBuilder.putRemark("key6", "value6");
return peopelInfoBuilder.build();
}
private static void testFastJSON2(PeopleInfoEntity peopleInfo, int count) {
long beg = System.currentTimeMillis();
String jsonStr = "";
PeopleInfoEntity peopleInfo1 = null;
for (int i = 0; i < count; i++) {
jsonStr = JSON.toJSONString(peopleInfo);
}
long end = System.currentTimeMillis();
System.out.printf("%d次 fastjson2 序列化耗时:%d ms;序列化后大小:%d\n", count, end - beg, jsonStr.length());
beg = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
peopleInfo1 = JSON.parseObject(jsonStr, PeopleInfoEntity.class);
}
end = System.currentTimeMillis();
System.out.printf("%d次 fastjson2 反序列化耗时:%d ms\n", count, end - beg);
}
private static void testJackson(PeopleInfoEntity peopleInfo, int count) throws JsonProcessingException {
long beg = System.currentTimeMillis();
String jsonStr = "";
PeopleInfoEntity peopleInfo1 = null;
ObjectMapper mapper = new ObjectMapper();
for (int i = 0; i < count; i++) {
jsonStr = mapper.writeValueAsString(peopleInfo);
}
long end = System.currentTimeMillis();
System.out.printf("%d次 jackson 序列化耗时:%d ms;序列化后大小:%d\n", count, end - beg, jsonStr.length());
beg = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
peopleInfo1 = mapper.readValue(jsonStr, PeopleInfoEntity.class);
}
end = System.currentTimeMillis();
System.out.printf("%d次 jackson 反序列化耗时:%d ms\n", count, end - beg);
}
private static void testPB(PeopleInfoPB peopleInfo, int count) throws InvalidProtocolBufferException {
long beg = System.currentTimeMillis();
byte[] jsonBytes = null;
PeopleInfoPB peopleInfo1 = null;
for (int i = 0; i < count; i++) {
jsonBytes = peopleInfo.toByteArray();
}
long end = System.currentTimeMillis();
System.out.printf("%d次 PB 序列化耗时:%d ms;序列化后大小:%d\n", count, end - beg, jsonBytes.length);
beg = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
peopleInfo1 = PeopleInfoPB.parseFrom(jsonBytes);
}
end = System.currentTimeMillis();
System.out.printf("%d次 PB 反序列化耗时:%d ms\n", count, end - beg);
}
public static void main(String[] args) throws JsonProcessingException, InvalidProtocolBufferException {
int count = 10000;
// entity对象
PeopleInfoEntity peopleInfoEntity = createEntity();
testFastJSON2(peopleInfoEntity, count);
testJackson(peopleInfoEntity, count);
// pb对象
PeopleInfoPB peopleInfoPB = createEntityPB();
testPB(peopleInfoPB, count);
}
}
发现PB的效率很快,而且序列化后文件大小也很小