在Java中使用protobuf序列化對象


 

 

什么是protobuf

它是一個對象序列化/反序列化的工具,什么是對象的序列化/反序列化?就是把一個Java堆中存活的對象轉換成一串二進制編碼,然后該編碼可以用於本地存儲和網絡傳輸。反序列化就是根據一串二進制編碼還原出原來的那個對象,protobuf能夠將一個對象以特定的格式轉換為一個二進制串(序列化),然后將二進制串還原成對象(反序列化)。這里涉及到兩個指標:

對同一個目標對象:

1)序列化和反序列化的時間開銷,

2)序列化之后串的長度

protobuf在這兩個方面都有非常出色的表現(網傳)

 

在Windows下使用protobuf的步驟如下:

第一步:

下載protoc-2.5.0-win32.zip,得到其中的protoc.exe.然后將該protoc.exe的存放路徑加入Path環境變量,便於訪問。比如,我的protoc.exe存放於D:/protobuf,環境變量中添加如下配置:

D:/protobuf

第二步:

編寫.proto文件,它是序列化一個對象的“模板”,protobuf就是根據它來決定如何序列化和反序列化。

編寫的person-entity.proto配置文件如下:

option java_outer_classname = "PersonEntity";//生成的數據訪問類的類名  
message Person {  
  required int32 id = 1;//同上  
  required string name = 2;//必須字段,在后面的使用中必須為該段設置值  
  optional string email = 3;//可選字段,在后面的使用中可以自由決定是否為該字段設置值
}  

message字段代表了一個對象,所以,可以使用message實現對象的嵌套序列化

required表示是強制字段,在后面的使用中必須為該字段設置值;

optional表示是可選字段,在后面的使用中可選地為該字段設置值;

repeated表示集合類型,可以填充多個數據

后面的1,2,3是字段的編號,字段名是讓用戶使用的,字段編號則是讓系統識別的,從1開始。

protobuf自定義的數據類型和Java的數據類型的對應關系見這篇博客https://blog.csdn.net/chuhui1765/article/details/100670318

 

可以指定字段類型為其他的Message字段類型:

message SearchResponse{
 repeated Result result = 1;
}

message Result{
 required string url = 1;
 optional string title = 2;
 repeated string snippets = 3;
}

可以在message內部嵌套另一個message:

message SearchResponse{

 
 message Result{
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
 }

 repeated Result result = 1;
}

有關protobuf更詳細的用法,可以參考這篇優秀的博文https://worktile.com/tech/share/prototol-buffers

第三步:

用protoc.exe生成PersonEntity.class。打開DOS窗口,輸入如下的編譯命令:

protoc.exe -I=D:/protobuf --java_out=D:/protobuf D:/protobuf/person-entity.proto

編譯命令的格式如下:

protoc.exe -I=.protoc文件的存放路徑 --java_out=.class文件的輸出路徑 .protoc文件的具體存放路徑

其中第三個路徑是必須的,而且要寫明.protoc文件

最后會生成PersonEntity.java文件,可以把它理解為一個工具類,幫助我們執行對象的序列化。

第四步:

新建maven項目,將PersonEntity.java復制到項目中,引入maven依賴

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

 

利用工具類序列化和反序列化對象。

/*
author:chxy
data:2020/4/1
description:
*/

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        //模擬將對象轉成byte[],方便傳輸
        PersonEntity.Person.Builder builder = PersonEntity.Person.newBuilder();
        builder.setId(1);
        builder.setName("abc");
        builder.setEmail("def");
        PersonEntity.Person person = builder.build();
        System.out.println("after :" +person.toString());
        final byte[] bytes = person.toByteArray();

        System.out.println("===========Person Byte==========");
        for(byte b : person.toByteArray()){
            System.out.print(b);
        }
        System.out.println();

        //反序列化成Person類
        byte[] byteArray =person.toByteArray();
        PersonEntity.Person person2 = PersonEntity.Person.parseFrom(byteArray);
        System.out.println("after :" +person2.toString());
    }
}

這里還犯過一個錯:另外自定義一個Person類與PersonEntity.Person相對應。實際上在我們的項目中可以直接利用這個PersonEntity.Person來實現功能需求。

 

下面同樣以這個Person類序列化為例,來實際地比較通過各種不同的序列化方式得到的的字節數組長度:

1.protobuf

序列化長度為:

System.out.println(person.getSerializedSize());

結果為:12

2.Java原生序列化

/*
author:chxy
data:2020/4/2
description:
*/

import java.io.*;

public class Test2 {

    public static void main(String[] args) throws IOException {

        Person p = new Person(1,"abc","def");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("E:/object.txt"));
        objectOutputStream.writeObject(p);

        FileInputStream fileInputStream = new FileInputStream("E:/object.txt");
        final int length = fileInputStream.read();
        System.out.println(length);
    }
}

結果為:172

3.通過對象的toString()

序列化:重寫對象的toString()方法,按照特定的格式,將對象的各個字段封裝進一個String字符串,再將字符串按照特定的編碼方式,轉換成byte數組,實現本地存儲和網絡傳輸。

反序列化:對byte數組,重新轉換成字符串,再將字符串按照約定的格式,還原成對象的屬性,從而構造出原來的對象。

這里重點探討的是:String對象轉換成byte數組之后的長度

public static void main(String[] args) {

        String s = "abc";
        System.out.println(s.getBytes().length);
        String s2 = "嚶嚶嚶";
        System.out.println(s2.getBytes().length);
        System.out.println(Charset.defaultCharset());
}

輸出結果為:

3

9

UTF-8

可見,如果是英文字母每個字符占一個字節,如果是漢字,每個字符占3個字節。如果通過這種方式序列化,它的空間效率也是非常高的。

這里有必要搞清楚Java默認的字符編碼集:

 /**
     * Encodes this {@code String} into a sequence of bytes using the * platform's default charset, storing the result into a new byte array.
     *
     * <p> The behavior of this method when this string cannot be encoded in
     * the default charset is unspecified.  The {@link
     * java.nio.charset.CharsetEncoder} class should be used when more control
     * over the encoding process is required.
     *
     * @return  The resultant byte array
     *
     * @since      JDK1.1
     */
    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

Java采用平台的默認字符編碼集來進行字符與二進制byte序列的轉換。當然也可以在序列化的時候顯示指定字符編碼集。

 

Unicode 與 UTF-8

為什么會出現unicode,這首先要從ASCII碼說起,它起源最早,用7位來表示英文字母、數字等字符。但是后來又出現了很多其他語言的字符,比如漢字,ASCII碼最多只能表示256個字符,無法支持這些”異域“字符,因此出現了unicode碼,將各種語言的字符用一種統一的編碼方式轉換成01序列。但是新的問題又出現了,unicode編碼效率不高,原來一個字符a用ASCII表示只需要一個字符,現在用unicode可能用3個字符,存儲效率和網絡傳輸效率大打折扣。因此出現了中間轉換編碼集,utf-8就是其中之一。”UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼,又稱萬國碼“,注意,它的編碼長度是可變的。所以,unicode是utf-8的基礎,utf-8是對unicode的一種解釋。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM