protocol buffer
介绍
protocol buffer简称protobuf。
在网络数据传输的时候,我们需要序列化和反序列化。
java自带的序列化和反序列化只支持java语言。如果client用python写,server用java写,这样就无法完成数据传输。
将数据序列化到xml文件中也行,但是很占空间,并且xml的树结构没有java的字段简单易读。
所以,我们使用一个google的rpc库:protobuf。
下载
首先我们要下载编译器:
在github上找到google的protobuf项目,并且下载适合自己电脑的编译器:
将编译器放在系统变量中:
这就算是安装完了。
下载对应的java库:
// https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java
compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.12.2'
// https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util
compile group: 'com.google.protobuf', name: 'protobuf-java-util', version: '3.12.2'
编写.proto文件
.proto
文件是你要存储的数据的描述文件。
在src下建一个文件:protobuf/addressbook.proto(必须要小写)
idea要安插件才能认识.proto
结尾的文件,你就按着它的提示安装就行了。
address book.proto:
syntax = "proto2";
package protobuftest;
option java_package = "com.ocean.protobuftest";
option java_outer_classname = "AddressBookProtos";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
syntax
是一个版本,提供错误检查。
package
指定生成的class文件在那个包中。java_package
是更加具体的指定包名。
java_outer_classname
指定生成的class文件的名字。也就是所谓的类名。
然后我们定义了两个message:Person和AddressBook。
message就像内部类一样。
在Person里面,还有一个message
:PhoneNumber,以及一个枚举类:PhoneType。
首先,看一下字段的基本数据类型:bool, int32, float, double, string
。
这和java极其像。
字段描述有required,optional,repeated
。
-
required表示必须赋值。
-
optional可以不赋值。如果不给,就用默认值。比如
optional PhoneType type = 2 [default = HOME];
PhoneType如果不赋值就默认为HOME。
如果你也没有默认值,就用系统默认值:
数值型的就是0,字符串就是空串,布尔型就是false。
- repeated就像一个数组。
repeated Person people = 1;
表示Person这个message可以有多个。
至于后面的=1
,=2
之类的,并非赋值,而是一个标记。在二进制编解码过程中确保字段的唯一性。
0-15的标记比16及以上的标记在编码的时候少一个字节,所以会用在较为常用的字段上,或者是repeated字段上。
编译.proto文件
命令:
-I是.proto
文件的目录。–java_out是目标目录。后面还跟着.proto
文件的地址。
最后的结果:
这个类是不能修改的。
向文件中写入消息
我们将构建Person对象和AddressBook对象。
这里的构建方法是建造者模式。比如:
import com.ocean.protobuftest.AddressBookProtos.Person;
public class TestBuild {
public static void main(String[] args) {
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhones(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
System.out.println(john.getId());
System.out.println(john.getName());
System.out.println(john.getEmail());
Person.PhoneNumber phone = john.getPhones(0);
System.out.println(phone.getNumber() + "\t" + phone.getType());
}
}
这就构建了一个Person实例。
我们在resources目录下建addressbook.txt
:
我们要把构建的电话本对象写到这个文件中:
package com.ocean.protobuftest;
import com.ocean.protobuftest.AddressBookProtos.AddressBook;
import com.ocean.protobuftest.AddressBookProtos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class AddPerson {
// This function fills in a Person message based on user input.
static AddressBookProtos.Person PromptForAddress(BufferedReader stdin,
PrintStream stdout) throws IOException {
Person.Builder person = Person.newBuilder();
stdout.println("Enter person ID: ");
person.setId(Integer.valueOf(stdin.readLine()));
stdout.println("Enter name: ");
person.setName(stdin.readLine());
stdout.println("Enter email address (blank for none): ");
String email = stdin.readLine();
if (email.length() > 0) {
person.setEmail(email);
}
while (true) {
stdout.println("Enter a phone number (or leave blank to finish): ");
String number = stdin.readLine();
if (number.length() == 0) {
break;
}
Person.PhoneNumber.Builder phoneNumber =
Person.PhoneNumber.newBuilder().setNumber(number);
stdout.println("Is this a mobile, home, or work phone? ");
String type = stdin.readLine();
if (type.equals("mobile")) {
phoneNumber.setType(Person.PhoneType.MOBILE);
} else if (type.equals("home")) {
phoneNumber.setType(Person.PhoneType.HOME);
} else if (type.equals("work")) {
phoneNumber.setType(Person.PhoneType.WORK);
} else {
stdout.println("Unknown phone type. Using default.");
}
person.addPhones(phoneNumber);
}
return person.build();
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE");
System.exit(-1);
}
AddressBook.Builder addressBook = AddressBook.newBuilder();
// Read the existing address book.
try {
addressBook.mergeFrom(new FileInputStream(args[0]));
} catch (FileNotFoundException e) {
System.out.println(args[0] + ": File not found. Creating a new file.");
}
// Add a person.
addressBook.addPeople(
PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
System.out));
// Write the new address book back to disk.
FileOutputStream output = new FileOutputStream(args[0]);
addressBook.build().writeTo(output);
output.close();
}
}
我们将获取到控制台的输出流,设置Person对象的属性。
在调用PromptForAddress
方法之前,程序会先去读文件中原来已经存在的数据。
加入动态参数:
运行:
如此addressbook.txt
就有值了。
现在我们要将其读出来。
从文件中读取消息
package com.ocean.protobuftest;
import java.io.FileInputStream;
import com.ocean.protobuftest.AddressBookProtos.Person;
import com.ocean.protobuftest.AddressBookProtos.AddressBook;
public class ListPeople {
// Iterates though all people in the AddressBook and prints info about them.
static void Print(AddressBook addressBook) {
for (Person person: addressBook.getPeopleList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
if (person.hasEmail()) {
System.out.println(" E-mail address: " + person.getEmail());
}
for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
switch (phoneNumber.getType()) {
case MOBILE:
System.out.print(" Mobile phone #: ");
break;
case HOME:
System.out.print(" Home phone #: ");
break;
case WORK:
System.out.print(" Work phone #: ");
break;
}
System.out.println(phoneNumber.getNumber());
}
}
}
// Main function: Reads the entire address book from a file and prints all
// the information inside.
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE");
System.exit(-1);
}
// Read the existing address book.
AddressBook addressBook =
AddressBook.parseFrom(new FileInputStream(args[0]));
Print(addressBook);
}
}
同样的,我们要加入动态参数:
运行:
我加了两个人,所以遍历出两个。