JamesB 发表于 2022-9-28 18:54

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("fengfeng")
                        .setEmail("fengfeng@126.com")
                        .addPhone(Person.PhoneNumber.newBuilder()
                              .setNumber("12323231")
                              .setType(Person.PhoneType.MOBILE))
                        .build();
    }
}每个消息和构建类包含一些其他方法,可以检测或者操作整个消息。如下:
isInitialized()
检查是否必要的fields已经设置
toString()
返回人类可读性的消息展示
mergeFrom(Message other)
合并其他消息的内容到该消息。
byte[] toByteArray();
序列化消息,返回一个字节数组
static Person parseFrom(byte[] data);
从字节数组里面解析消息
static Person parseFrom(InputStream input);
从输入流读取并解析一个消息
项目结构


页: [1]
查看完整版本: Google Protobuf教程