Google Protobuf Java API详解

阅读数:479 评论数:0

跳转到新版页面

分类

python/Java

正文

0. 入门介绍

Protobuf有两个大版本,proto2和proto3,proto3相对proto2而言,简言之就是支持更多的语言(Ruby、C#等 ),删除了一些复杂的语法和特性,引入了更多的约定等 。

凡事皆有双面性,XML、JSON更注重数据结构化,关注人类可读性和语义表达能力,Protobuf更注重数据序列化,关注效率、空间、速度,人类可读性差,语义表达能力不足,所以我们需要借助.proto定义数据结构的文件来更友好的使用Protobuf。

在idea中可以安装Protobuf相关插件来实现.proto文件转java类。

-----------------下面是proto3---------------------------------------------------------------------------

1、编写proto文件

定义一个JetProtos.proto文件

syntax = "proto3"; // PB协议版本

import "google/protobuf/any.proto"; // 引用外部的message,可以是本地的,也可以是此处比较特殊的 Any

package jet.protobuf; // 包名,其他 proto 在引用此 proto 的时候使用
// 注意:和下面的 java_package 是两种易混淆概念,同时定义的时候,java_package 具有较高的优先级

option java_package = "com.jet.protobuf"; // 生成类的包名,注意:会在指定路径下按照该包名的定义来生成文件夹
option java_outer_classname="PersonTestProtos"; // 生成类的类名,注意:下划线的命名会在编译的时候被自动改为驼峰命名

message PersonTest {  
    int32 id = 1; // int 类型  ,编号是1,编号不能重复
    string name = 2; // string 类型  
    string email = 3;  
    Sex sex = 4; // 枚举类型  
    repeated PhoneNumber phone = 5; // 引用下面定义的 PhoneNumber 类型的 message  
    map<string, string> tags = 6; // map 类型  
    repeated google.protobuf.Any details = 7; // 使用 google 的 any 类型  

    // 定义一个枚举  
    enum Sex {      
        DEFAULT = 0;      
        MALE = 1;      
        Female = 2;  
    }  
    
    // 定义一个 message  
    message PhoneNumber {    
        string number = 1;    
        PhoneType type = 2;    
        
        enum PhoneType {      
            MOBILE = 0;      
            HOME = 1;      
            WORK = 2;    
        }  
        
    }
    
}

2、编译成java文件

可以从github上下载安装,也可以使用idea插件操作。

3、maven依赖

<!-- protobuf -->
<dependency>     
    <groupId>com.google.protobuf</groupId>     
    <artifactId>protobuf-java</artifactId>     
    <version>3.7.1</version>
</dependency>

4、序列化和反序列化

package com.jet.mini.protobuf;

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * @ClassName: ProtoTest
 * @Description: ProtoBuf 测试
 * @Author: Jet.Chen
 * @Date: 2019/5/8 9:55
 * @Version: 1.0
 **/
public class ProtoTest {

    public static void main(String[] args) {
        try {
            /** Step1:生成 personTest 对象 */
            // personTest 构造器
            PersonTestProtos.PersonTest.Builder personBuilder = PersonTestProtos.PersonTest.newBuilder();
            // personTest 赋值
            personBuilder.setName("Jet Chen");
            personBuilder.setEmail("ckk505214992@gmail.com");
            personBuilder.setSex(PersonTestProtos.PersonTest.Sex.MALE);

            // 内部的 PhoneNumber 构造器
            PersonTestProtos.PersonTest.PhoneNumber.Builder phoneNumberBuilder = PersonTestProtos.PersonTest.PhoneNumber.newBuilder();
            // PhoneNumber 赋值
            phoneNumberBuilder.setType(PersonTestProtos.PersonTest.PhoneNumber.PhoneType.MOBILE);
            phoneNumberBuilder.setNumber("17717037257");

            // personTest 设置 PhoneNumber
            personBuilder.addPhone(phoneNumberBuilder);

            // 生成 personTest 对象
            PersonTestProtos.PersonTest personTest = personBuilder.build();

            /** Step2:序列化和反序列化 */
            // 方式一 byte[]:
            // 序列化
//            byte[] bytes = personTest.toByteArray();
            // 反序列化
//            PersonTestProtos.PersonTest personTestResult = PersonTestProtos.PersonTest.parseFrom(bytes);
//            System.out.println(String.format("反序列化得到的信息,姓名:%s,性别:%d,手机号:%s", personTestResult.getName(), personTest.getSexValue(), personTest.getPhone(0).getNumber()));



            // 方式二 ByteString:
            // 序列化
//            ByteString byteString = personTest.toByteString();
//            System.out.println(byteString.toString());
            // 反序列化
//            PersonTestProtos.PersonTest personTestResult = PersonTestProtos.PersonTest.parseFrom(byteString);
//            System.out.println(String.format("反序列化得到的信息,姓名:%s,性别:%d,手机号:%s", personTestResult.getName(), personTest.getSexValue(), personTest.getPhone(0).getNumber()));



            // 方式三 InputStream
            // 粘包,将一个或者多个protobuf 对象字节写入 stream
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            personTest.writeDelimitedTo(byteArrayOutputStream);
            // 反序列化,从 steam 中读取一个或者多个 protobuf 字节对象
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            PersonTestProtos.PersonTest personTestResult = PersonTestProtos.PersonTest.parseDelimitedFrom(byteArrayInputStream);
            System.out.println(String.format("反序列化得到的信息,姓名:%s,性别:%d,手机号:%s", personTestResult.getName(), personTest.getSexValue(), personTest.getPhone(0).getNumber()));

        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

 

-----------------下面是proto2---------------------------------------------------------------------------

1.依赖

想要正常的使用生成的Java类,需要导入protobuf的依赖:protobuf-java.jar

2.protobuf Java API

以GPS信号为例,Gps.proto文件如下:

syntax = "proto2";
 
option java_package = "com.test.bean";
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;
}

(1)Builders

使用Protobuf生成的每一个java类中,都会包含两种内部类:Msg和Msg包含的Builder。

以上面的GPS信号为例,

AddressBookProtos.AddressBook、AddressBookProtos.Person、AddressBookProtos.Person.PhoneNumber
AddressBookProtos.AddressBook.Builder、AddressBookProtos.Person.Builder、
AddressBookProtos.Person.PhoneNumber.Builder


这两个类提供不同的API,具体来说:

Buider提供了构建类,查询类的API(set、get、has、clear等方法)

Msg提供了查询、序列化的API。

public class Main {
    public static void main(String[] args) {
        //使用builder构建一个Person对象
        AddressBookProtos.Person john =
                AddressBookProtos.Person.newBuilder()
                        .setId(1234)
                        .setName("John Doe")
                        .setEmail("jdoe@example.com")
                        .addPhones(
                                //内部类PhoneNumber同样使用其Builder构建,但是注意,不需要调用build()方法
                                AddressBookProtos.Person.PhoneNumber.newBuilder()
                                        .setNumber("555-4321")
                                        .setType(AddressBookProtos.Person.PhoneType.HOME))
                        //build()结束整个程序流,返回Person对象
                        .build();
        System.out.println(john);
    }
}
//Msg只提供了get和has方法
String email = john.getEmail();
boolean hasEmail = john.hasEmail();
 
//而builder提供了add/set、get、has和clear方法
AddressBookProtos.Person.Builder builder = AddressBookProtos.Person.newBuilder();
builder.setEmail("jdoe@example.com");
boolean hasEmail1 = builder.hasEmail();
String email1 = builder.getEmail();
builder.clearEmail();



序列化:

  • byte[] toBytesArray():生成字节数组
  • void writeTo(OutputStresam output) :序列化并写入到指定的输出流中

反序列化:

  • static Person parseFrom(byte[] data):解析二进制数组,反序列化指定对象
  • static Person parseFrom(InputStream input):解析输入流,反序列化指出指定对象
public class Main {
    public static void main(String[] args) {
        //使用builder()Msg类
        AddressBookProtos.Person john =
                AddressBookProtos.Person.newBuilder()
                        .setId(1234)
                        .setName("John Doe")
                        .setEmail("jdoe@example.com")
                        .addPhones(
                                AddressBookProtos.Person.PhoneNumber.newBuilder()
                                        .setNumber("555-4321")
                                        .setType(AddressBookProtos.Person.PhoneType.HOME))
                        .build();
        try {
            //序列化
            byte[] bytes = john.toByteArray();
            //反序列化
            System.out.println(AddressBookProtos.Person.parseFrom(bytes));
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
}

最后欢迎大家访问我的个人网站:1024s




相关推荐

1、基本类型 在程序设计中经常用到一系列类型(基本类型),它们需要特殊对待。对于这些类型,Java采取与C和C++相同的方法,也就是说,不用new来创建变量,于是创建一个并非引

1、直接常量 为了编译器可以准确的知道要生成什么样的类型,可以给直接常量后面添加后缀字符标志它的类型,若为L表示long,F表示float,D表示double。也可以利用前缀表示进制,0x表示十六进制

Java完全采用动态内存分配方式。每当想创建新对象时,就需要使用new关键字来构建此对象实例。 1、this 在构造器中,如果为this添加了参数列表,那么就有了

一、类的继承 1、说明 (1)extends关键字用于类的继承。 (2)在C++中,方法的动态绑定是使用virtual关键字来实现的,而在Java中,动态绑定是默认的形为,不需要添加额外的关键字。 (

1、类型信息 指程序能够在运行时发现和使用类型信息,我们一般使用两种方式来实现运行时对象和类的信息:传统的RTTI和反射机制。 (1)class对象 <p

用于描述Java源代码,使得我们能够以将由编译器来测试和验证的格式,存储有关程序的额外信息。使用时在@后面跟注解的名字。 1、预定义的三个注解<

一、创建线程 创建线程有四种方式:继承Thread类、实现Runnable接口、实现Callable接口、通过线程池创建。 1、继承Thread 重写run方法。 class A extends Th

一、Collection接口 Collection接口的iterator和toArray方法都用于获得集合中的“所有元素”。前者返回一个“iterator”对象,后者返回一个包含集合中所有元素的数组。

1.hashCode的存在主要用于查找的快捷性,如hashtable,hastmap等,hashcode是用来在散列存储结构中确定对象的存储地址的。 2.如果两个对象相同,就是适用

Java中的数据类型,可分为两类: 1.基本数据类型,它们之间的比较,应用双等号,比较的是它们的值。 2.复合数据类型(类) 当他们用双等号进行比较的时