日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

hive序列生成_常见的序列化框架及Protobuf原理

發布時間:2025/3/11 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hive序列生成_常见的序列化框架及Protobuf原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
享學課堂作者:逐夢々少年
轉載請聲明出處!

上次我們詳細的學習了Java中的序列化機制,但是我們日常開發過程中,因為java的序列化機制的壓縮效率問題,以及序列化大小帶來的傳輸的效率問題,一般很少會使用原生的序列化機制,而是使用常見的序列化開源框架來實現序列化操作,接下來我們學習一下開發常用的序列化機制及原理分析

常見的序列化框架

xml序列化

在java發展早期開始,為了統一接口,xml協議橫空出世,良好的可讀性,自由度極高的擴展性,成了很長一段時間的序列化標準規范。實現xml序列化/反序列化的方案有很多,最常見的是XStream 和 Java 自帶的 XML 序列化和反序列化兩種 ,并且還有基于xml協議的soap協議實現的webservice接口等。可以說xml序列化是開發中最常見也是發展時間最久的協議,并且支持跨進程和跨語言交互,但是缺陷也很明顯,即xml規范下的每一個屬性和值都是固定的標簽形式,導致序列化后的字節流文件很大,遠超于java自身的序列化方案,而且效率很低,一般建議使用在內部系統或者性能要求不高,但是對于接口的復雜度和準確性要求比較高的接口交互,或者適合多語言多進程之間交互的統一規范,不適合QPS過高的工程使用

JSON序列化

在xml序列化發展了多年后,也浮現了一些問題,比如開發并不簡便,解析xml復雜度較高,還有xml的標準規范比較多,自由度過高,導致很難有效的指定格式校驗等,于是一種新的輕量級的序列化交互的方案--JSON(JavaScript Object Notation)出現了,相對于xml來說,json格式語法簡單,自由度較高,有很高的可讀性,并且在JSON序列化后的字節流小于xml序列化的結果,解析起來更方便,于是基于JSON的接口成了新的標準規范之一,到現在也出現了很多JSON的序列化/反序列化的開源框架,比如開發中最常見到的Jackson、阿里巴巴開源的FastJson、谷歌的GSON等,而這三種框架各有優劣,通過測試一萬個對象的序列化和反序列化的效率,對比如下:

序列化:

反序列化:

可以看出來序列化的時候,Gson的速度明顯稍微慢了一些,Jackson反而最快,而在反序列化的時候,三個表現都很穩定,時間都差不多,但是當數據比較大的時候,測試結果又有所不同,測試結果和數據來自https://blog.csdn.net/Sword52888/article/details/81062575 提供的代碼和腳本,可以得出對應結論:

  • 1、 當數據小于 100K 的時候,建議使用 Gson
  • 2、 當數據100K 與 1M 的之間時候,建議使用各個JSON引擎性能差不多
  • 3、 當數據大與 1M 的時候,建議使用 JackSon 與 FastJson

而在穩定性上面,默認情況下Gson在各種情況下的表現最好,Jackson配合對應的配置化也能達到很好的穩定性,而FastJson表現的不穩定,所以對于這幾種json庫的使用,建議環境較復雜場景下使用JackSon,加上自定義的配置化可以更靈活的處理更多的場景,但是在復雜度一般,僅僅在乎性能的場景下,建議使用FastJson,因為FastJson的api更易用,依賴少,簡單場景下使用簡單

Hessian序列化

Hessian是一個支持跨語言傳輸的二進制文本序列化協議,對比Java默認的序列化,Hessian的使用較簡單,并且性能較高,現在的主流遠程通訊框架幾乎都支持Hessian,比如Dubbo,默認使用的就是Hessian,不過是Hessian的重構版

Avro序列化

Avro序列化設計初衷是為了支持大批量數據交換的應用,支持二進制序列化方式,并且自身提供了動態語言支持,可以更加便捷、快速處理大批量的Avro數據

Kyro序列化

Kyro序列化是主流的比較成熟的序列化方案之一,目前廣泛使用在大數據組件中,比如Hive、Storm等,性能比起Hessian還要優越,但是缺陷較明顯,不支持跨語言交互,在dubbo2.6.x版本開始已經加入了Kyro序列化的支持

Protobuf序列化

Protobuf是谷歌提出的序列化方案,不同的是此方案獨立于語言、平臺,谷歌提供了多個語言如java、c、go、python等語言的實現,也提供了多平臺的庫文件支持,使用比較廣泛,優點在于性能開銷很小,壓縮率很高,但是缺陷也很明顯,可讀性很差,并且protobuf需要使用特定語言的庫進行翻譯轉換,使用起來較為麻煩

Protobuf序列化的使用

首先現在使用Protobuf,有手動編譯和maven依賴jar兩種方案,實際開發中我們一般使用maven坐標引入jar,坐標如下:

<dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.0.8</version></dependency><dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.0.8</version></dependency>

編寫一個便捷的序列化轉換工具類:

package com.demo.utils;import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtobufIOUtil; import com.dyuproject.protostuff.runtime.RuntimeSchema;public class SerializeUtils{/****序列化方法*/public static <T> byte[] serialize(T t,Class<T> clazz) {return ProtobufIOUtil.toByteArray(t, RuntimeSchema.createFrom(clazz),LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));}/****反序列化方法*/public static <T> T deSerialize(byte[] data,Class<T> clazz) {RuntimeSchema<T> runtimeSchema = RuntimeSchema.createFrom(clazz);T t = runtimeSchema.newMessage();ProtobufIOUtil.mergeFrom(data, t, runtimeSchema);return t;}

使用的時候直接使用工具類進行自動的轉換傳輸即可

注:使用的時候注意jdk版本和jar版本的兼容問題,并且需要序列化的實體并不需要實現Serializable 接口

當然,我們接下來手動編譯protobuf使用,了解下protobuf的語法以及原理

手動編譯Protobuf

手動編譯protobuf我們需要一個Protobuf編譯器的支持,這里推薦直接點擊地址,在github上下載:

https://github.com/google/protobuf/releases

或者直接百度云:http://pan.baidu.com/s/1gefsM9X 下載,這里博主選擇直接百度云集成的環境下載

1:解壓protoc-3.0.0-beta-2-win32會得到一個protoc.exe的文件.

2:解壓protobuf-3.0.0-beta-2.(3.0.0-beta是版本號,可能會有所不同)

3.將protoc.exe文件放到2步驟解壓后文件夾java/src/這個目錄里面(src里面,不是跟src并級)

4.WINDOS+R 輸入cmd命令并切換至3步驟的src目錄的上級目錄,就是java目錄下會發現這個目錄有個POM文件,使用maven編譯命令編譯(mvn install),然后會在java目錄下生成target以及一個jar。OK到目前位置,安裝算完成了

接下來是編譯環節,將上面生成的那個jar和一開始的那個exe文件放到需要編譯文件的同一目錄下 ,使用編譯指令(cmd):

protoc --java_out=xxx/xxx.proto

如果出現:Missing input file錯誤,那么就使用 以下指令:

protoc xxx/xxx.proto --java_out=./

接下來,我們開始編寫一個protobuf的簡單demo,后綴為proto,代碼如下:

syntax="proto2"; package com.demo.serial; option java_package = "com.demo.serial"; option java_outer_classname="UserProtos"; message User { required string name=1; required int32 age=2; }

首先我們先看看上面編寫的內容分別代表什么意思:

syntax="proto2";

這里指定了protobuf編譯的版本,目前主流為proto2,當然也有不少選擇最新的proto3版本,而每個大版本之間的差異還是很大的,具體區別參見官方說明:https://developers.google.com/protocol-buffers/docs/proto3

接著是:

option java_package = "com.demo.serial"

這里指定的是上一行我們設置的package對應java文件里面的package名稱

option java_outer_classname="UserProtos"

這里指定了如果編譯完畢生成的java類的名稱

message User

這里的message代表給User類指定對應屬性類型

required string name=1

這里出現了一個特殊的修飾符類型required,在protobuf中,有如下幾種修飾符:

  • required: 格式良好的 message 必須包含該字段一次。
  • optional: 格式良好的 message 可以包含該字段零次或一次(不超過一次)。
  • repeated: 該字段可以在格式良好的消息中重復任意多次(包括零)。其中重復值的順序會被保留。
注意:在proto3版本中,為了兼容性考慮,required修飾符已經取消

完成這些以后,我們使用指令:

protoc --java_out=xxx/xxx.proto

生成protobuf轉換后的實體類,然后我們在pom中引入:

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

然后進行序列化:

UserProtos.User user=UserProtos.User.newBuilder().setAge(300).setName("Mic").build(); byte[] bytes=user.toByteArray(); for(byte bt:bytes){ System.out.print(bt+" "); }

我們將這個結果打印出來的字節如下:

10 3 77 105 99 16 -84 2

可以看出來序列化的數值看不明白,但是的確字節數很小,說明protobuf進行了算法壓縮,那么我們就要了解下protobuf壓縮算法相關的詳細操作,首先我們要知道protobuf的type對應的各個語言的類型:

.proto TypeNotesC++ TypeJava TypePython Type[2]Go Typedoubledoubledoublefloat*float64floatfloatfloatfloat*float32int32使用可變長度編碼。編碼負數的效率低 - 如果你的字段可能有負值,請改用 sint32int32intint*int32int64使用可變長度編碼。編碼負數的效率低 - 如果你的字段可能有負值,請改用 sint64int64longint/long[3]*int64uint32使用可變長度編碼uint32int[1]int/long[3]*uint32uint64使用可變長度編碼uint64long[1]int/long[3]*uint64sint32使用可變長度編碼。有符號的 int 值。這些比常規 int32 對負數能更有效地編碼int32intint*int32sint64使用可變長度編碼。有符號的 int 值。這些比常規 int64 對負數能更有效地編碼int64longint/long[3]*int64fixed32總是四個字節。如果值通常大于 228,則比 uint32 更有效。uint32int[1]int/long[3]*uint32fixed64總是八個字節。如果值通常大于 256,則比 uint64 更有效。uint64long[1]int/long[3]*uint64sfixed32總是四個字節int32intint*int32sfixed64總是八個字節int64longint/long[3]*int64boolboolbooleanbool*boolstring字符串必須始終包含 UTF-8 編碼或 7 位 ASCII 文本stringStringstr/unicode[4]*stringbytes可以包含任意字節序列stringByteStringstr[]byte

Protobuf序列化的原理分析

了解了Protobuf的type轉換的格式以后,我們再來看,Protobuf的存儲格式,Protobuf采用了T-L-V的存儲格式存儲數據,其中的T代表tag,即key,L則是length,代表當前存儲的類型的數據長度,當是數值類型的時候L被忽略,V代表value,即存入的值,protobuf會將每一個key根據不同的類型對應的序列化算法進行序列化,然后按照keyvaluekeyvalue的格式存儲,其中key的type類型與對應的壓縮算法關系如下:

write_type編碼方式type存儲方式0Varint(負數使用Zigzag輔助)int32、int64、uint32、uint64、sint32、sint64、bool、enumT-V164-bitfixed、sfixed64、doubleT-V2Length-delimistring、bytes、embedded、messages、packed repeated fieldsT-L-V3(棄用)Start groupGroups(deprecated)棄用4(棄用)End groupGroups(deprecated)棄用532-bitfixed32、sfixed32、floatT-V

需要注意的是protobuf的key計算按照(field_number << 3) | wire_type 方式計算,而這里的field_number是指定義的時候該字段的域號,如:required string name=1;這里的name字段的域號為1,在protobuf中規定:

  • 如果域號在[1,15]范圍內,會使用一個字節表示Key;
  • 如果域號大于等于16,會使用兩個字節表示Key;

key編碼完成后,該字節的第一個比特位表示后一個字節是否與當前字節有關系,即:

  • 如果第一個比特位為1,表示有關,即連續兩個字節都是Key的編碼;
  • 如果第一個比特位為0,表示Key的編碼只有當前一個字節,后面的字節是Length或者Value;
注意:protobuf中的域號定義要小于2048 ,原因為,最大的域號即2個字節16個比特位表示key,去掉位移的三位,還剩下13位,再去掉兩個字節開頭的第一個用來表示是否存在關系的比特位,即16-3-2=11,最后只有11位參與計算,二進制計算后2^11== 2048 ,所以域號不得超過2048

了解了以上的那些,我們看看,上述我們編寫的案例,算法是如何實現的呢?

varint編碼

上述我們的案例中,出現了int32類型,對應的壓縮算法為varint,我們看下age=300,這個值是如何序列化的

可以看出來,我們首先將300轉為二進制,結果為100101100,由于當前是int32,所以不足32位,高位全部補0,即為00000000000000000000000100101100,接著第二步,從低位到高位取7位,8位是一個字節,當前的最高位為標志位,如果下一個字節內還有非0得數值(即有意義存在),則最高位補1,如果沒有最高位補0,當最高位為0后,壓縮存儲結束,從age=300,我們可以看出來,取7位則是0101100,由于后一個字節中還存在值,所以最高位補1,則為10101100,而下一個字節則從第8位(低位到高位)開始,繼續獲取7個字節,則為0000010,由于后續的一個字節中,不存在有意義的值,則最高位補0,代表后續不存在有意義的值了,不需要繼續壓縮,則為00000010,也就是說原本32個比特位的數值,現在只有16個比特位,4個字節壓縮到了2個字節,而我們都知道計算機中,高位為1代表負數,計算機中對負數的計算為先將結果取反后,再去補碼操作,而負數的補碼則是在反碼的基礎上+1,那么我們現在將結果反過來,先去-1,得到反碼,則為10101011,再去取反,得到原碼,則為01010100,現在我們將這個值轉換為十進制,則可以知道結果為84,由于高位為1,則代表是負數,最終結果為-84,而00000010由于高位是0,代表本身為正數,正數的原碼反碼補碼都是自身,所以直接轉換為十進制結果為2,現在我們把這兩個結果和上述打印的結果比較一下,是不是發現是一樣的?當然,我們也從這個過程中發現了一些問題,比如小于128的值,我們甚至只需要1個字節就能存儲完畢,但是如果我們需要存儲的值很大,超過了268435455以后的數值,甚至需要五個字節來存儲(超過28個有效比特位),但是絕大多數情況下,我們都不會使用這么大的數值,所以一般情況下,我們都能比之前使用更小的字節存儲,達到壓縮的目的

字符串壓縮

在Protobuf中存儲字符串格式,使用的T-L-V存儲方式,標識符Tag采用Varint編碼,字節長度Length采用Varint編碼,string類型字段值采用UTF-8編碼方式存儲,所以tag得值為1 <<3 | 2 =10,L的值存儲為00000011,即為3,而V的存儲,把每一個字符按照UTF-8的編碼后的字節流數組,分別為77 105 99,而在Protobuf編碼后的字節流則是按照如圖的順序,所以打印出來的結果如上的10 3 77 105 99 16 -84 2

負數存儲-write_type為0

在計算機中,負數會被表示為很大的整數,因為計算機定義負數符號位為數字的最高位,所
以如果采用 varint 編碼表示一個負數,那么一定需要 5 個比特位。所以在 protobuf 中通過
sint32/sint64 類型來表示負數,負數的處理形式是先采用 zigzag 編碼(把符號數轉化為無符
號數),在采用 varint 編碼。
sint32:(n << 1) ^ (n >> 31)
sint64:(n << 1) ^ (n >> 63)
比如存儲一個(-300)的值
-300
原碼:0001 0010 1100
取反:1110 1101 0011
加 1 :1110 1101 0100
n<<1: 整體左移一位,右邊補 0 -> 1101 1010 1000
n>>31: 整體右移 31 位,左邊補 1 -> 1111 1111 1111
n<<1 ^ n >>31
1101 1010 1000 ^ 1111 1111 1111 = 0010 0101 0111
十進制: 0010 0101 0111 = 599
然后再使用varint 算法得到兩個字節
1101 0111(-41),0000 0100(4)

總結

基于Protobuf序列化原理分析,為了有效降低序列化后數據量的大小,可以采用以下措施:

  • 字段標識號(Field_Number)盡量只使用1-15,且不要跳動使用 Tag是需要占字節空間的。如果Field_Number>16時,Field_Number的編碼就會占用2個字節,那么Tag在編碼時就會占用更多的字節;如果將字段標識號定義為連續遞增的數值,將獲得更好的編碼和解碼性能
  • 若需要使用的字段值出現負數,請使用sint32/sint64,不要使用int32/int64。 采用sint32/sint64數據類型表示負數時,會先采用Zigzag編碼再采用Varint編碼,從而更加有效壓縮數據
  • 對于repeated字段,盡量增加packed=true修飾 增加packed=true修飾,repeated字段會采用連續數據存儲方式,即T - L - V - V -V方式
  • 2.將結果賦值給a,則a = 3

    public static void main(String[] args){int a = 8;a ^= 11;//a = 8^11 = 3System.out.println(a);//3}既然來了,點個關注再走唄~

    總結

    以上是生活随笔為你收集整理的hive序列生成_常见的序列化框架及Protobuf原理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。