google的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("[email protected]")
						.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);
	}
}

同樣的,我們要加入動態參數:

運行:

我加了兩個人,所以遍歷出兩個。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章