有什么好的建站公司,襄阳企业网站建设,微网站设计平台,资阳网目录#xff1a;
需求#xff1a;字段规则消息类型的定义与使用通讯录2.0的写⼊实现TestRead.java(通讯录2.0)TestRead.java(通讯录2.0) 另⼀种验证⽅法--toString()enum类型升级通讯录⾄2.1版本Any类型oneof类型map类型默认值更新消息保留字段reserved未知字段选项option 通…目录
需求字段规则消息类型的定义与使用通讯录2.0的写⼊实现TestRead.java(通讯录2.0)TestRead.java(通讯录2.0) 另⼀种验证⽅法--toString()enum类型升级通讯录⾄2.1版本Any类型oneof类型map类型默认值更新消息保留字段reserved未知字段选项option 通讯录4.0实现---⽹络版序列化能⼒对⽐验证总结
1.需求
不再打印联系⼈的序列化结果⽽是将通讯录序列化后并写⼊⽂件中。从⽂件中将通讯录解析出来并进⾏打印。新增联系⼈属性共包括姓名、年龄、电话信息、地址、其他联系⽅式、备注。
2.字段规则
消息的字段可以⽤下⾯⼏种规则来修饰
singular消息中可以包含该字段零次或⼀次不超过⼀次。proto3语法中字段默认使⽤该规则。repeated消息中可以包含该字段任意多次包括零次其中重复值的顺序会被保留。可以理解为定义了⼀个数组。 我们在 src/main/proto/proto3 ⽬录下新建 contacts.proto ⽂件内容如下
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名message PeopleInfo{string name 1;int32 age 2;repeated string phone_numbers 3;
}PeopleInfo 消息中新增phone_numbers 字段表⽰⼀个联系⼈有多个号码所以将其设置为repeated。
3.消息类型的定义与使⽤
定义
在单个.proto⽂件中可以定义多个消息体且⽀持定义嵌套类型的消息任意多层。每个消息体中的字段编号可以重复。更新contacts.proto我们可以将phone_number提取出来单独成为⼀个消息 使⽤
消息类型可作为字段类型使⽤
contacts.proto 可导⼊其他.proto⽂件的消息并使⽤
例如Phone消息定义在phone.proto⽂件中
syntax proto3;
package phone;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname PhoneProtos; // 编译后⽣成的proto包装类的类名message Phone{string number 1;
}
contacts.proto
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名import start/phone.proto;message PeopleInfo{string name 1;int32 age 2;repeated phone.Phone phone 3;
}
运行结果 3.创建通讯录2.0版本
通讯录2.x的需求是向⽂件中写⼊通讯录列表以上我们只是定义了⼀个联系⼈的消息并不能存放通讯录列表所以还需要在完善⼀下contacts.proto(终版通讯录2.0)
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名message PeopleInfo{string name 1;int32 age 2;message Phone{string number 1;}repeated Phone phone 3;}message Contacts{repeated PeopleInfo contacts 1;
}
接着使⽤maven插件进⾏⼀次编译这次编译会多⽣成五个⽂件 Contacts.java ContactsOrBuilder.java ContactsProtos.java PeopleInfo.java PeopleInfoOrBuilder.java 。 可以看出由于我们设置了option java_multiple_files true; 会给⽣成的每个⾃定义 message 类都⽣成两个对应的⽂件。
在message 类中主要包含
获取字段值的get⽅法⽽没有set⽅法。序列化在MessageLite中定义和反序列化⽅法。newBuilder()静态⽅法⽤来创建Builder。
在 Builder 类中主要包含
包含⼀个build()⽅法主要是⽤来构造出⼀个⾃定义类对象。编译器为每个字段提供了获取和设置⽅法以及能够操作字段的⼀些⽅法。
且在上述的例⼦中
对于builder每个字段都有⼀个clear_⽅法可以将字段重新设置回empty状态。mergeFrom(Message other)合并other的内容到这个message中如果是单数域则覆盖如果是重复值则追加连接。对于使⽤repeated修饰的字段也就是数组类型pb为我们提供了⼀系列add⽅法来新增⼀个值或⼀个builder并且提供了getXXXCount()⽅法来获取数组存放元素的个数。 4.通讯录2.0的写⼊实现
TestWrite.java(通讯录2.0)
package testcode;import com.example.start.Contacts;
import com.example.start.PeopleInfo;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/start/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/start/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);System.out.print(请输⼊联系⼈年龄: );int age scan.nextInt();peopleBuilder.setAge(age);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);peopleBuilder.addPhone(phoneBuilder);}System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}运行结果 5.TestRead.java(通讯录2.0)
package testcode;import com.example.start.Contacts;
import com.example.start.PeopleInfo;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/start/contacts.bin));// 打印printContacts(contacts);}private static void printContacts(Contacts contacts) {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber());}}}
}运行结果 6.TestRead.java(通讯录2.0) 另⼀种验证⽅法--toString()
在⾃定义消息类的⽗抽象类AbstractMessage中重写了toString()⽅法。该⽅法返回的内容是⼈类可读的对于调试特别有⽤。例如在TestRead类的main函数中调⽤⼀下
package testcode;import com.example.start.Contacts;
import com.example.start.PeopleInfo;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/start/contacts.bin));// 打印
// printContacts(contacts);System.out.println(contacts.toString());}
//
// private static void printContacts(Contacts contacts) {
// for (int i 0; i contacts.getContactsCount(); i) {
// System.out.println(--------------联系⼈ (i 1) -----------);
// PeopleInfo peopleInfo contacts.getContacts(i);
// System.out.println(姓名: peopleInfo.getName());
// System.out.println(年龄: peopleInfo.getAge());
// int j 1;
// for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
// System.out.println(电话 (j) : phone.getNumber());
// }
// }
// }
}运行结果在这⾥是将utf-8汉字转为⼋进制格式输出了 7. enum类型
定义规则
语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为 枚举类型名称 使⽤驼峰命名法⾸字⺟⼤写。例如 MyEnum 常量值名称 全⼤写字⺟多个字⺟之间⽤ _ 连接。例如 ENUM_CONST 0; 我们可以定义⼀个名为PhoneType的枚举类型定义如下 enum PhoneType { MP 0; //移动电话 TEL l; //固定电话 } 要注意枚举类型的定义有以下⼏种规则
0值常量必须存在且要作为第⼀个元素。这是为了与proto2的语义兼容第⼀个元素作为默认值且值为0。枚举类型可以在消息外定义也可以在消息体内定义嵌套。枚举的常量值在32位整数的范围内。但因负值⽆效因⽽不建议使⽤与编码规则有关。
定义时注意
将两个具有相同枚举值名称的枚举类型放在单个.proto⽂件下测试时编译后会报错某某某常
量已经被定义所以这⾥要注意
同级同层的枚举类型各个枚举类型中的常量不能重名。单个.proto⽂件下最外层枚举类型和嵌套枚举类型不算同级。多个.proto⽂件下若⼀个⽂件引⼊了其他⽂件且每个⽂件都未声明package每个proto⽂件中的枚举类型都在最外层算同级。多个.proto⽂件下若⼀个⽂件引⼊了其他⽂件且每个⽂件都声明了package不算同级。 8.升级通讯录⾄2.1版本
更新contacts.proto(通讯录2.1)新增枚举字段并使⽤更新内容如下
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名message PeopleInfo{string name 1;int32 age 2;message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话}message Contacts{repeated PeopleInfo contacts 1;
}
接着使⽤maven插件进⾏⼀次编译。 更新TestWrite.java(通讯录2.1)
package testcode;import com.example.start.Contacts;
import com.example.start.PeopleInfo;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/start/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/start/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);System.out.print(请输⼊联系⼈年龄: );int age scan.nextInt();peopleBuilder.setAge(age);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);System.out.print(选择此电话类型 (1、移动电话 2、固定电话) : );int type scan.nextInt();scan.nextLine();switch (type) {case 1:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);break;case 2:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);break;default:System.out.println(⾮法选择使⽤默认值);break;}peopleBuilder.addPhone(phoneBuilder);}System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}运行结果 更新TestRead.java(通讯录2.1)
package testcode;import com.example.start.Contacts;
import com.example.start.PeopleInfo;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/start/contacts.bin));// 打印printContacts(contacts);//System.out.println(contacts.toString());}private static void printContacts(Contacts contacts) {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber() ( phone.getType().name() ));}}}
}运行结果 9.Any类型
字段还可以声明为Any类型可以理解为泛型类型。使⽤时可以在Any中存储任意消息类型。Any类型的字段也⽤repeated来修饰。Any类型是google已经帮我们定义好的类型在装ProtoBu时其中的include⽬录下查找所有google已经定义好的.proto⽂件。
升级通讯录⾄2.2版本
通讯录2.2版本会新增联系⼈的地址信息我们可以使⽤any类型的字段来存储地址信息。 更新contacts.proto(通讯录2.2)更新内容如下
contacts.proto
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名import google/protobuf/any.proto;message PeopleInfo{string name 1;int32 age 2;message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话google.protobuf.Any data 4;
}message Contacts{repeated PeopleInfo contacts 1;
}message Address{string home_address 1;string unit_address 2;
}
TestWrite.java
package testcode;import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.Any;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/start/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/start/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);System.out.print(请输⼊联系⼈年龄: );int age scan.nextInt();peopleBuilder.setAge(age);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);System.out.print(选择此电话类型 (1、移动电话 2、固定电话) : );int type scan.nextInt();scan.nextLine();switch (type) {case 1:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);break;case 2:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);break;default:System.out.println(⾮法选择使⽤默认值);break;}peopleBuilder.addPhone(phoneBuilder);}Address.Builder addressBulider Address.newBuilder();System.out.println(请输入联系人家庭地址);String homeAddress scan.nextLine();addressBulider.setHomeAddress(homeAddress);System.out.println(请输入联系人单位地址);String unitAddress scan.nextLine();addressBulider.setUnitAddress(unitAddress);peopleBuilder.setData(Any.pack(addressBulider.build()));System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}TestRead.java
package testcode;import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/start/contacts.bin));// 打印printContacts(contacts);//System.out.println(contacts.toString());}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber() ( phone.getType().name() ));}if (peopleInfo.hasData() peopleInfo.getData().is(Address.class)) {Address address peopleInfo.getData().unpack(Address.class);if (!address.getHomeAddress().isEmpty()) {System.out.println(家庭地址 address.getHomeAddress());}if (!address.getUnitAddress().isEmpty()) {System.out.println(单位地址 address.getUnitAddress());}}}}
}运行结果 10.oneof类型
如果消息中有很多可选字段并且将来同时只有⼀个字段会被设置那么就可以使⽤ oneof 加强这个⾏为也能有节约内存的效果。
升级通讯录⾄2.3版本
通讯录2.3版本想新增联系⼈的其他联系⽅式⽐如qq或者微信号⼆选⼀我们就可以使⽤oneof字 段来加强多选⼀这个⾏为。oneof字段定义的格式为 oneof 字段名 { 字段1; 字段2; ... } 更新contacts.proto(通讯录2.3)更新内容如下
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名import google/protobuf/any.proto;message PeopleInfo{string name 1;int32 age 2;message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话google.protobuf.Any data 4;oneof other_contact{string qq 5;string wechat 6;}
}message Contacts{repeated PeopleInfo contacts 1;
}message Address{string home_address 1;string unit_address 2;
}
TestWrite.java
package testcode;import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.Any;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/start/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/start/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);System.out.print(请输⼊联系⼈年龄: );int age scan.nextInt();peopleBuilder.setAge(age);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);System.out.print(选择此电话类型 (1、移动电话 2、固定电话) : );int type scan.nextInt();scan.nextLine();switch (type) {case 1:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);break;case 2:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);break;default:System.out.println(⾮法选择使⽤默认值);break;}peopleBuilder.addPhone(phoneBuilder);}Address.Builder addressBulider Address.newBuilder();System.out.println(请输入联系人家庭地址);String homeAddress scan.nextLine();addressBulider.setHomeAddress(homeAddress);System.out.println(请输入联系人单位地址);String unitAddress scan.nextLine();addressBulider.setUnitAddress(unitAddress);peopleBuilder.setData(Any.pack(addressBulider.build()));System.out.println(请选择要添加的其他联系方式1.qq号 2.微信号);int otherContact scan.nextInt();scan.nextLine();if (1 otherContact) {System.out.println(请输入qq号);String qq scan.nextLine();peopleBuilder.setQq(qq);} else if (2 otherContact) {System.out.println(请输入微信号);String wechat scan.nextLine();peopleBuilder.setWechat(wechat);} else {System.out.println(无效选择设置失败);}System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}运行结果 TestRead.java
package testcode;import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/start/contacts.bin));// 打印printContacts(contacts);//System.out.println(contacts.toString());}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber() ( phone.getType().name() ));}if (peopleInfo.hasData() peopleInfo.getData().is(Address.class)) {Address address peopleInfo.getData().unpack(Address.class);if (!address.getHomeAddress().isEmpty()) {System.out.println(家庭地址 address.getHomeAddress());}if (!address.getUnitAddress().isEmpty()) {System.out.println(单位地址 address.getUnitAddress());}}switch (peopleInfo.getOtherContactCase()) {case QQ:System.out.println(qq号 peopleInfo.getQq());break;case WECHAT:System.out.println(微信号 peopleInfo.getWechat());break;case OTHERCONTACT_NOT_SET:break;}}}
}运行结果 11.map类型
语法⽀持创建⼀个关联映射字段也就是可以使⽤map类型去声明字段类型格式为 mapkey_type, value_type map_field N; 要注意的是
key_type 是除了float和bytes类型以外的任意标量类型。 value_type 可以是任意类型。map字段不可以⽤repeated修饰map中存⼊的元素是⽆序的
升级通讯录⾄2.4版本
最后通讯录2.4版本想新增联系⼈的备注信息我们可以使⽤map类型的字段来存储备注信息。 更新contacts.proto通讯录2.4)更新内容如下
contacts.proto
syntax proto3;
package start;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.start; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名import google/protobuf/any.proto;message PeopleInfo{string name 1;int32 age 2;message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话google.protobuf.Any data 4;oneof other_contact{string qq 5;string wechat 6;}mapstring, string remark 7;}message Contacts{repeated PeopleInfo contacts 1;
}message Address{string home_address 1;string unit_address 2;
}
TestWrite.java
package testcode;import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.Any;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/start/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/start/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);System.out.print(请输⼊联系⼈年龄: );int age scan.nextInt();peopleBuilder.setAge(age);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);System.out.print(选择此电话类型 (1、移动电话 2、固定电话) : );int type scan.nextInt();scan.nextLine();switch (type) {case 1:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);break;case 2:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);break;default:System.out.println(⾮法选择使⽤默认值);break;}peopleBuilder.addPhone(phoneBuilder);}Address.Builder addressBulider Address.newBuilder();System.out.println(请输入联系人家庭地址);String homeAddress scan.nextLine();addressBulider.setHomeAddress(homeAddress);System.out.println(请输入联系人单位地址);String unitAddress scan.nextLine();addressBulider.setUnitAddress(unitAddress);peopleBuilder.setData(Any.pack(addressBulider.build()));System.out.println(请选择要添加的其他联系方式1.qq号 2.微信号);int otherContact scan.nextInt();scan.nextLine();if (1 otherContact) {System.out.println(请输入qq号);String qq scan.nextLine();peopleBuilder.setQq(qq);} else if (2 otherContact) {System.out.println(请输入微信号);String wechat scan.nextLine();peopleBuilder.setWechat(wechat);} else {System.out.println(无效选择设置失败);}for (int i 0; ; i) {System.out.println(请输入备注 (i 1) 标题只输入回车完成备注新增);String key scan.nextLine();if (key.isEmpty()) {break;}System.out.println(请输入备注内容);String value scan.nextLine();peopleBuilder.putRemark(key, value);}System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}运行结果 TestRead.java
package testcode;import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/start/contacts.bin));// 打印printContacts(contacts);//System.out.println(contacts.toString());}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber() ( phone.getType().name() ));}if (peopleInfo.hasData() peopleInfo.getData().is(Address.class)) {Address address peopleInfo.getData().unpack(Address.class);if (!address.getHomeAddress().isEmpty()) {System.out.println(家庭地址 address.getHomeAddress());}if (!address.getUnitAddress().isEmpty()) {System.out.println(单位地址 address.getUnitAddress());}}switch (peopleInfo.getOtherContactCase()) {case QQ:System.out.println(qq号 peopleInfo.getQq());break;case WECHAT:System.out.println(微信号 peopleInfo.getWechat());break;case OTHERCONTACT_NOT_SET:break;}for (Map.EntryString, String entry : peopleInfo.getRemarkMap().entrySet()) {System.out.println( entry.getKey() : entry.getValue());}}}
}运行结果 12.默认值
反序列化消息时如果被反序列化的⼆进制序列中不包含某个字段反序列化对象中相应字段时就会设置为该字段的默认值。不同的类型对应的默认值不同
对于字符串默认值为空字符串。对于字节默认值为空字节。对于布尔值默认值为false。对于数值类型默认值为0。对于枚举默认值是第⼀个定义的枚举值必须为0。对于消息字段未设置该字段。它的取值是依赖于语⾔。对于设置了repeated的字段的默认值是空的通常是相应语⾔的⼀个空列表。对于 消息字段 、 oneof字段 和 any字段 都有has⽅法来检测当前字段是否被设置。
13.更新消息
更新规则 如果现有的消息类型已经不再满⾜我们的需求例如需要扩展⼀个字段在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可
禁⽌修改任何已有字段的字段编号。若是移除⽼字段要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号reserved以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。int32uint32int64uint64和bool是完全兼容的。可以从这些类型中的⼀个改为另⼀个⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配可能会被截断例如若将64位整数当做32位进⾏读取它将被截断为32位。sint32和sint64相互兼容但不与其他的整型兼容。string和bytes在合法UTF-8字节前提下也是兼容的。bytes包含消息编码版本的情况下嵌套消息与bytes也是兼容的。fixed32与sfixed32兼容fixed64与sfixed64兼容。enum与int32uint32int64和uint64兼容注意若值不匹配会被截断。但要注意当反序列化消息时会根据语⾔采⽤不同的处理⽅案例如未识别的proto3枚举类型会被保存在消息中但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。oneof 将⼀个单独的值更改为新oneof类型成员之⼀是安全和⼆进制兼容的。若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新oneof类型也是可⾏的。将任何字段移⼊已存在的oneof类型是不安全的。
移除老字段错误示例
proto.update.client contacts.proto(移除字段之前)
syntax proto3;
package client;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.update.client; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名message PeopleInfo{string name 1;int32 age 2;message Phone {string number 1; // 电话号码}repeated Phone phone 3; // 电话}message Contacts{repeated PeopleInfo contacts 1;
}proto.update.service contacts.proto移除字段age 重行编译一下使用maven插件 TestWrite.java package com.example.update.service;import com.example.update.service.Contacts.Builder;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/service/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/update/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);// System.out.print(请输⼊联系⼈年龄: );
// int age scan.nextInt();
// peopleBuilder.setAge(age);
// scan.nextLine();System.out.print(请输⼊联系⼈生日: );int bir scan.nextInt();peopleBuilder.setBirthday(bir);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);peopleBuilder.addPhone(phoneBuilder);}System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}运行结果 TestRead.java package com.example.update.client;import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/update/contacts.bin));// 打印printContacts(contacts);}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber());}}}
}运行结果 结论不能重复使用字段编号不建议直接删除或注释掉字段。 14.保留字段reserved
如果通过删除或注释掉字段来更新消息类型未来的⽤⼾在添加新字段时有可能会使⽤以前已经 存在但已经被删除或注释掉的字段编号。将来使⽤该.proto的旧版本时的程序会引发很多问题数据损坏、隐私错误等等。确保不会发⽣这种情况的⼀种⽅法是使⽤ reserved 将指定字段的编号或名称设置为保留项。当我们再使⽤这些编号或名称时protocol buffer的编译器将会警告这些编号或名称不可⽤。举个例⼦
message Message {// 设置保留项reserved 100, 101, 200 to 299;reserved field3, field4;// 注意不要在⼀⾏ reserved 声明中同时声明字段编号和名称。// reserved 102, field5;// 设置保留项之后下⾯代码会告警int32 field1 100; //告警Field field1 uses reserved number 100int32 field2 101; //告警Field field2 uses reserved number 101int32 field3 102; //告警Field name field3 is reservedint32 field4 103; //告警Field name field4 is reserved
}
15.未知字段
在通讯录3.0版本中我们向service⽬录下的contacts.proto新增了‘⽣⽇’字段但对于client相 关的代码并没有任何改动。验证后发现新代码序列化的消息service也可以被旧代码client解析。并且这⾥要说的是新增的‘⽣⽇’字段在旧程序client中其实并没有丢失⽽是会作为旧程序的未知字段。
未知字段解析结构良好的protocol buffer已序列化数据中的未识别字段的表⽰⽅式。例如当旧程序解析带有新字段的数据时这些新字段就会成为旧程序的未知字段。本来proto3在解析消息时总是会丢弃未知字段但在3.5版本中重新引⼊了对未知字段的保留机制。所以在3.5或更⾼版本中未知字段在反序列化时会被保留同时也会包含在序列化的结果中。
代码示例
service.contacts.proto client.contacts.proto service.TestWrite.java
package com.example.update.service;import com.example.update.service.Contacts.Builder;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Builder contactsBuilder Contacts.newBuilder();// 读取已存在的contactstry {contactsBuilder.mergeFrom(newFileInputStream(src/main/java/com/example/service/contacts.bin));} catch (FileNotFoundException e) {System.out.println(contacts.bin not found. Creating a new file.);}// 新增⼀个联系⼈contactsBuilder.addContacts(addPeopleInfo());// 将新的contacts写回磁盘FileOutputStream output new FileOutputStream(src/main/java/com/example/update/contacts.bin);contactsBuilder.build().writeTo(output);output.close();}private static PeopleInfo addPeopleInfo() {Scanner scan new Scanner(System.in);PeopleInfo.Builder peopleBuilder PeopleInfo.newBuilder();System.out.println(-------------新增联系⼈-------------);System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);// System.out.print(请输⼊联系⼈年龄: );
// int age scan.nextInt();
// peopleBuilder.setAge(age);
// scan.nextLine();System.out.print(请输⼊联系⼈生日: );int bir scan.nextInt();peopleBuilder.setBirthday(bir);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新 增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);peopleBuilder.addPhone(phoneBuilder);}System.out.println(-------------添加联系⼈成功-------------);return peopleBuilder.build();}
}运行结果 client.TestRead.java
package com.example.update.client;import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/update/contacts.bin));// 打印printContacts(contacts);}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber());}System.out.println(未知字段内容\n peopleInfo.getUnknownFields());}}
}运行结果 未知字段从哪获取? 在PeopleInfo.java 的PeopleInfo 类中有个 getUnknownFields() ⽅法⽤来获取未知字段
public final com.google.protobuf.UnknownFieldSet getUnknownFields() {...}
UnknownFieldSet类介绍
UnknownFieldSet包含在分析消息时遇到但未由其类型定义的所有字段。
public final class UnknownFieldSet implements MessageLite {private final TreeMapInteger, Field fields;public MapInteger, Field asMap() {...}public boolean hasField(int number) {...}public Field getField(int number) {...}// ----------------- 重写了 toString ----------------public String toString() {...}// ------------------------builder------------------public static final class Builder implements MessageLite.Builder {public Builder clear() {...}public Builder clearField(int number) {...}public boolean hasField(int number) {...}public Builder addField(int number, Field field) {...}public MapInteger, Field asMap() {...}}public static final class Field {private ListLong varint;private ListInteger fixed32;private ListLong fixed64;private ListByteString lengthDelimited;private ListUnknownFieldSet group;public ListLong getVarintList() {...}public ListInteger getFixed32List() {...}public ListLong getFixed64List() {...}public ListByteString getLengthDelimitedList() {...}public ListUnknownFieldSet getGroupList() {...}// 省略了 Field Builder : 是⼀些处理字段的⽅法例如设置、获取、清理}
}
升级通讯录3.1版本---验证未知字段
更新 TestRead.java (通讯录3.1)在这个版本中需要打印出未知字段的内容。更新的代码如下
package com.example.update.client;import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 从磁盘⽂件⾥读取并反序列化为 Message 实例Contacts contacts Contacts.parseFrom(new FileInputStream(src/main/java/com/example/update/contacts.bin));// 打印printContacts(contacts);}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {for (int i 0; i contacts.getContactsCount(); i) {System.out.println(--------------联系⼈ (i 1) -----------);PeopleInfo peopleInfo contacts.getContacts(i);System.out.println(姓名: peopleInfo.getName());System.out.println(年龄: peopleInfo.getAge());int j 1;for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println(电话 (j) : phone.getNumber());}System.out.println(未知字段内容\n peopleInfo.getUnknownFields());}}
}
其他⽂件均不⽤做任何修改运⾏Client下的main函数可得如下结果 前后兼容性 根据上述的例⼦可以得出pb是具有向前兼容的。为了叙述⽅便把增加了“⽣⽇”属的TestWirte.java称为“新模块”未做变动的TestRead.java称为“⽼模块”。
向前兼容⽼模块能够正确识别新模块⽣成或发出的协议。这时新增加的“⽣⽇”属性会被当作未知字段pb3.5版本及之后。向后兼容新模块也能够正确识别⽼模块⽣成或发出的协议。前后兼容的作⽤当我们维护⼀个很庞⼤的分布式系统时由于你⽆法同时升级所有模块为了保证在升级过程中整个系统能够尽可能不受影响就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。
16.选项option
.proto⽂件中可以声明许多选项使⽤option 标注。选项能影响proto编译器的某些处理⽅式。
选项分类:
选项的完整列表在 google/protobuf/descriptor.proto 中定义。部分代码
syntax proto2; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
由此可⻅选项分为 ⽂件级、消息级、字段级 等等但并没有⼀种选项能作⽤于所有的类型。 JAVA常⽤选项列举
java_multiple_files编译后⽣成的⽂件是否分为多个⽂件该选项为⽂件选项。 java_package编译后⽣成⽂件所在的包路径该选项为⽂件选项。 java_outer_classname编译后⽣成的proto包装类的类名该选项为⽂件选项。 allow_alias允许将相同的常量值分配给不同的枚举常量⽤来定义别名。该选项为枚举选项。 举个例⼦
enum PhoneType {option allow_alias true;MP 0;TEL 1;LANDLINE 1; // 若不加 option allow_alias true; 这⼀⾏会编译报错
}
设置⾃定义选项:
https://protobuf.dev/programming-guides/proto2/17.通讯录4.0实现---⽹络版 需求
Protobuf还常⽤于通讯协议、服务端数据交换场景。那么在这个⽰例中我们将实现⼀个⽹络版本的通讯录模拟实现客⼾端与服务端的交互通过Protobuf来实现各端之间的协议序列化。 需求如下
客⼾端向服务端发送联系⼈信息并接收服务端返回的响应。服务端接收到联系⼈信息后将结果打印出来。客⼾端、服务端间的交互数据使⽤Protobuf来完成。 proto.internet.client.contacts.proto
syntax proto3;
package client;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.internet.client.dto; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名message PeopleInfoRequest {string name 1; // 姓名int32 age 2; // 年龄message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话mapstring, string remark 4; // 备注
}message PeopleInfoResponse {string uid 1;
} proto.internet.service.contacts.proto
syntax proto3;
package service;option java_multiple_files true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package com.example.internet.service.dto; // 编译后⽣成⽂件所在的包路径
option java_outer_classname ContactsProtos; // 编译后⽣成的proto包装类的类名message PeopleInfoRequest {string name 1; // 姓名int32 age 2; // 年龄message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话mapstring, string remark 4; // 备注
}message PeopleInfoResponse {string uid 1;
}
com.example.internet.client.BytesUtils.java
package com.example.internet.client;public class BytesUtils {/*** 获取 bytes 有效⻓度* param bytes* return*/public static int getValidLength(byte[] bytes){int i 0;if (null bytes || 0 bytes.length)return i;for (; i bytes.length; i) {if (bytes[i] \0)break;}return i;}/*** 截取 bytes* param b* param off* param length* return*/public static byte[] subByte(byte[] b,int off,int length){byte[] b1 new byte[length];System.arraycopy(b, off, b1, 0, length);return b1;}
}com.example.internet.client.ContactsClient.java
package com.example.internet.client;import com.example.internet.client.dto.PeopleInfoRequest;
import com.example.internet.client.dto.PeopleInfoResponse;
import com.example.internet.client.BytesUtils;
import java.io.*;
import java.net.*;
import java.util.Scanner;public class ContactsClient {private static final SocketAddress ADDRESS new InetSocketAddress(localhost, 8888);public static void main(String[] args) throws IOException {// 创建客⼾端 DatagramSocketDatagramSocket socket new DatagramSocket();// 构造 request 请求数据PeopleInfoRequest request createRequest();// 序列化 requestbyte[] requestData request.toByteArray();// 创建 request 数据报DatagramPacket requestPacket new DatagramPacket(requestData,requestData.length, ADDRESS);// 发送 request 数据报socket.send(requestPacket);System.out.println(发送成功);// 创建 response 数据报⽤于接收服务端返回的响应byte[] udpResponse new byte[1024];DatagramPacket responsePacket new DatagramPacket(udpResponse,udpResponse.length);// 接收 response 数据报socket.receive(responsePacket);// 获取有效的 responseint length BytesUtils.getValidLength(udpResponse);byte[] reqsponseData BytesUtils.subByte(udpResponse, 0, length);// 反序列化 response打印结果PeopleInfoResponse response PeopleInfoResponse.parseFrom(reqsponseData);System.out.printf(接收到服务端返回的响应%s, response.toString());}private static PeopleInfoRequest createRequest() {System.out.println(------输⼊需要传输的联系⼈信息-----);Scanner scan new Scanner(System.in);PeopleInfoRequest.Builder peopleBuilder PeopleInfoRequest.newBuilder();System.out.print(请输⼊联系⼈姓名: );String name scan.nextLine();peopleBuilder.setName(name);System.out.print(请输⼊联系⼈年龄: );int age scan.nextInt();peopleBuilder.setAge(age);scan.nextLine();for (int i 0; ; i) {System.out.print(请输⼊联系⼈电话 (i 1) (只输⼊回⻋完成电话新增): );String number scan.nextLine();if (number.isEmpty()) {break;}PeopleInfoRequest.Phone.Builder phoneBuilder PeopleInfoRequest.Phone.newBuilder();phoneBuilder.setNumber(number);peopleBuilder.addPhone(phoneBuilder);}for (int i 0; ; i) {System.out.print(请输⼊备注 (i 1) 标题 (只输⼊回⻋完成备注新增): );String remarkKey scan.nextLine();if (remarkKey.isEmpty()) {break;}System.out.print(请输⼊备注 (i 1) 内容: );String remarkValue scan.nextLine();peopleBuilder.putRemark(remarkKey, remarkValue);}System.out.println(------------输⼊结束-----------);return peopleBuilder.build();}
}
com.example.internet.service.BytesUtils.java
package com.example.internet.service;public class BytesUtils {/*** 获取 bytes 有效⻓度** param bytes* return*/public static int getValidLength(byte[] bytes) {int i 0;if (null bytes || 0 bytes.length)return i;for (; i bytes.length; i) {if (bytes[i] \0)break;}return i;}/*** 截取 bytes** param b* param off* param length* return*/public static byte[] subByte(byte[] b, int off, int length) {byte[] b1 new byte[length];System.arraycopy(b, off, b1, 0, length);return b1;}
}
com.example.internet.service.ContactsService.java
package com.example.internet.service;import com.example.internet.service.dto.PeopleInfoRequest;
import com.example.internet.service.dto.PeopleInfoResponse;
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;public class ContantsService {//服务器socket要绑定固定的端⼝private static final int PORT 8888;public static void main(String[] args) throws IOException {// 创建服务端DatagramSocket指定端⼝可以发送及接收UDP数据报DatagramSocket socket new DatagramSocket(PORT);// 不停接收客⼾端udp数据报while (true){System.out.println(等待接收UDP数据报...);// 创建 request 数据报⽤于接收客⼾端发送的数据byte[] udpRequest new byte[1024];// 1m1024kb, 1kb1024byte,//UDP最多64k包含UDP⾸部8byteDatagramPacket requestPacket new DatagramPacket(udpRequest, udpRequest.length);// 接收 request 数据报在接收到数据报之前会⼀直阻塞socket.receive(requestPacket);// 获取有效的 requestint length BytesUtils.getValidLength(udpRequest);byte[] requestData BytesUtils.subByte(udpRequest, 0, length);// 反序列化 requestPeopleInfoRequest request PeopleInfoRequest.parseFrom(requestData);System.out.println(接收到请求数据:);System.out.println(request.toString());// 构造 responsePeopleInfoResponse response PeopleInfoResponse.newBuilder().setUid(111111111).build();// 序列化 responsebyte[] responseData response.toByteArray();// 构造 response 数据报注意接收的客⼾端数据报包含IP和端⼝号要设置到响应//的数据报中DatagramPacket responsePacket new DatagramPacket(responseData, responseData.length, requestPacket.getSocketAddress());// 发送 response 数据报socket.send(responsePacket);}}
}
运行结果 19.序列化能⼒对⽐验证
在这⾥让我们分别使⽤PB与JSON的序列化与反序列化能⼒对值完全相同的⼀份结构化数据进⾏不同次数的性能测试。为了可读性下⾯这⼀份⽂本使⽤JSON格式展⽰了需要被进⾏测试的结构化数据内容 20.总结 总结
XML、JSON、ProtoBuf都具有数据结构化和数据序列化的能⼒。XML、JSON更注重数据结构化关注可读性和语义表达能⼒。ProtoBuf更注重数据序列化关注效率、空间、速度可读性差语义表达能⼒不⾜为保证极致的效率会舍弃⼀部分元信息。ProtoBuf的应⽤场景更为明确XML、JSON的应⽤场景更为丰富。