Google Protobuf教程
背景在介绍Protobuf之前,首先来看下什么是序列化,序列化表示保存或写一个对象的状态到文件的过程。但是严格来说,序列化表示转化一个来自Java所支持的对象到网络所支持对象的过程。
其实Java有默认的序列化方式,但是存在以下限制:
[*]Java默认的实现方式并不是高效的,以及存在有些问题
[*]当想与其他应用(C++或者Python)共享数据时,Java默认的序列化支持的并不好
[*]当序列化类的实例已被序列化,然后对实现默认序列化类做修改,那么就不能从序列化的形势转化成修改后的对象。JVM会为每个序列化类生成一个版本ID,并持久化该版本ID到序列化流中。当序列化类已做修改后,那么该版本ID就会变化,可能会就会跟序列化流中的版本ID不匹配了,然后报InvalidClassCast异常。
[*]序列化和反序列化是个相当耗资源(CPU和IO)的过程。
既然Java的序列化存在以上问题,那么接下来介绍下Google protocol buffers(protobuf),它是一个序列化对象的有效方式,比XML更快,更简单,比JSON更压缩。它被设计是与语言无关的,而且可扩展。当前protobuf完成了对C++,C#,Go,Java,Python的支持。这个教程是介绍对Java语言的支持。
protobuf存在以下优点:
[*]所有数据都是类型化的
[*]数据完成自动压缩(更少的CPU使用)
[*]protocol比XML小3-10倍
[*]protocol比XML快10-100倍
[*]形成的数据访问类可以很容易被程序使用
[*]数据可以跨语言访问(C#,Java,Go,Python,JavaScript等)
[*]代码自动为你生成
但是protobuf也存在一些缺点:
[*]对部分语言的支持缺少(但是主流语言支持的还是很好的)
[*]不能使用文本编辑器打开(因为他是压缩的,而且是序列化的)
基本概念
每个.proto文件是以一个包声明开始的,这样防止名字冲突,然后以消息格式的方式定义数据如何组织。
这个.proto文件要被protoc编译器使用,会生成一个Java类,包含getter和setter方法,然后你可以使用这些方法进行序列化和反序列化对象。例如,下面的消息格式:
message User {
required string name = 1;
required int32 id = 2; // Id of the User
optional string email = 3; // Email of the User
}消息格式中值类型可以使用numbers,booleans,string,bytes,collections,enumerations,当然也开始嵌套其他消息类型,这样允许你层级式组织数据,类似于JSON一样。
消息格式中的Field可以配置为optional,required,repeated。
required表示初始化值必须被提供,否则field不能被初始化
optional表示如果没有初始化,那么默认的值被提供。
repeated表示该field可以出现多次(包含0次),而且值的顺序被保留。由于历史原因,如果是数值类型的重复fields不能进行 很好的编码。新的代码需要使用得到更好的性能。例如,
repeated int32 samples = 4可以使用//进行注释
使用protocol编译器可以生成对应语言的文件,例如,对于C++来说,每个.proto文件生成一个.h和.cc文件。
对Java来说,会为每个消息类型生成一个java文件,而且为创建消费类实例的构建类。对于其他语言来说,各有各的不同。
Java实例
首先定义一个proto文件
syntax = "proto2";
package com.dufeng;
option java_package = "com.dufeng.protobuf";
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;
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}当包声明后,针对Java有两个特殊的选项,分别是:java_package和java_outer_classname,java_package配置你生成的类文件应该属于哪个包,如果你不配置,它仅仅对应包声明的包中。java_outer_classname定义一个类名,它包含文件中的所有类。如果你不配置java_outer_classname,那么将会使用转化后的文件名,例如,my_proto.proto文件会将MyProto作为类名。然后就可以开始定义消息格式。例如,Person,AddressBook。
编译Protocol文件
使用protoc编译器进行编译,生成对应语言的文件(c++,java,或者python),可以配置源目录,如果没有配置,那么当前目录被使用。
protoc -I=. --java_out=. tutorial.proto执行完成后,就会在当前目录下生成com/dufeng/protobuf/AddressBookProtos.java文件
在java文件中,对于每个field包含getters和setters方法。部分内容如下:
// Generated by the protocol buffer compiler.DO NOT EDIT!
// source: tutorial.proto
package com.dufeng.protobuf;
public final class AddressBookProtos {
private AddressBookProtos() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
public interface PersonOrBuilder extends
// @@protoc_insertion_point(interface_extends:com.dufeng.Person)
com.google.protobuf.MessageOrBuilder {
/**
* <code>required string name = 1;</code>
* @return Whether the name field is set.
*/
boolean hasName();
/**
* <code>required string name = 1;</code>
* @return The name.
*/
java.lang.String getName();
/**
* <code>required string name = 1;</code>
* @return The bytes for name.
*/
com.google.protobuf.ByteString
getNameBytes();
/**
* <code>required int32 id = 2;</code>
* @return Whether the id field is set.
*/
boolean hasId();
/**
* <code>required int32 id = 2;</code>
* @return The id.
*/
int getId();
/**
* <code>optional string email = 3;</code>
* @return Whether the email field is set.
*/
boolean hasEmail();
/**
* <code>optional string email = 3;</code>
* @return The email.
*/
java.lang.String getEmail();
/**
* <code>optional string email = 3;</code>
* @return The bytes for email.
*/
com.google.protobuf.ByteString
getEmailBytes();
/**
* <code>repeated .com.dufeng.Person.PhoneNumber phone = 4;</code>
*/
java.util.List<com.dufeng.protobuf.AddressBookProtos.Person.PhoneNumber>
getPhoneList();
/**
* <code>repeated .com.dufeng.Person.PhoneNumber phone = 4;</code>
*/
com.dufeng.protobuf.AddressBookProtos.Person.PhoneNumber getPhone(int index);
/**
* <code>repeated .com.dufeng.Person.PhoneNumber phone = 4;</code>
*/
int getPhoneCount();
/**
* <code>repeated .com.dufeng.Person.PhoneNumber phone = 4;</code>
*/
java.util.List<? extends com.dufeng.protobuf.AddressBookProtos.Person.PhoneNumberOrBuilder>
getPhoneOrBuilderList();
/**
* <code>repeated .com.dufeng.Person.PhoneNumber phone = 4;</code>
*/
com.dufeng.protobuf.AddressBookProtos.Person.PhoneNumberOrBuilder getPhoneOrBuilder(
int index);
}构建消息
protoc生成的java文件使用builder的方式进行构建。
import com.dufeng.protobuf.AddressBookProtos;
import com.dufeng.protobuf.AddressBookProtos.*;
public class JavaProtoExample {
public static void main(String[] args) {
Person person =
Person.newBuilder()
.setId(123)
.setName(&#34;fengfeng&#34;)
.setEmail(&#34;fengfeng@126.com&#34;)
.addPhone(Person.PhoneNumber.newBuilder()
.setNumber(&#34;12323231&#34;)
.setType(Person.PhoneType.MOBILE))
.build();
}
}每个消息和构建类包含一些其他方法,可以检测或者操作整个消息。如下:
isInitialized()
检查是否必要的fields已经设置
toString()
返回人类可读性的消息展示
mergeFrom(Message other)
合并其他消息的内容到该消息。
byte[] toByteArray();
序列化消息,返回一个字节数组
static Person parseFrom(byte[] data);
从字节数组里面解析消息
static Person parseFrom(InputStream input);
从输入流读取并解析一个消息
项目结构
页:
[1]