Hadoop Serialization hadoop序列化詳解(最新版) (1)【java和hadoop序列化比較和writable接口】

初學java的人確定對java序列化記憶猶新。最開始不少人並不會一會兒理解序列化的意義所在。這樣子是由於不少人仍是對java最底層的特性不是特別理解,當你經驗豐富,對java理解更加深入以後,你就會發現序列化這種東西的精髓。

談hadoop序列化以前,咱們再來回顧一下java的序列化,也是最底層的序列化:

在面向對象程序設計中,類是個很重要的概念。所謂「類」,能夠將它想像成建築圖紙,而對象就是根據圖紙蓋的大樓。類,規定了對象的一切。根據建築圖紙造房子,蓋出來的就是大樓,等同於將類進行實例化,獲得的就是對象。
   
 一開始,在源代碼裏,類的定義是明確的,但對象的行爲有些地方是明確的,有些地方是不明確的。對象裏不明確地方,是由於對象在運行的時候,須要處理沒法預測的事情,諸如用戶點了下屏幕,用戶點了下按鈕,輸入點東西,或者須要從網絡發送接收數據之類的。後來,引入了泛型的概念以後,類也開始不明確了,若是使用了泛型,直到程序運行的時候,才知道到底是哪一種對象須要處理。
對象能夠很複雜,也能夠跟時序相關。通常來講,「活的」對象只生存在內存裏,關機斷電就沒有了。通常來講,「活的」對象只能由本地的進程使用,不能被髮送到網絡上的另一臺計算機。想象一下,對象怎麼出現的,通常是new出來的,new出來的對象在內存裏面,另外的計算機怎麼可能使用我這臺機器上的對象呢?若是要的話,序列化就是你必需要使用的東西。
 序列化,能夠存儲「活的」對象,能夠將「活的」對象發送到遠程計算機。
 把「活的」對象序列化,就是把「活的」對象轉化成一串字節,而「反序列化」,就是從一串字節裏解析出「活的」對象。因而,若是想把「活的」對象存儲到文件,存儲這串字節便可,若是想把「活的」對象發送到遠程主機,發送這串字節便可,須要對象的時候,作一下反序列化,就能將對象「復活」了。
有例子爲證:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public  class  Person  implements  Serializable {
 
     private  String name =  null ;
 
     private  Integer age =  null ;
 
     private  Gender gender =  null ;
 
     public  Person() {
         System.out.println( "none-arg constructor" );
     }
 
     public  Person(String name, Integer age, Gender gender) {
         System.out.println( "arg constructor" );
         this .name = name;
         this .age = age;
         this .gender = gender;
     }
 
     public  String getName() {
         return  name;
     }
 
     public  void  setName(String name) {
         this .name = name;
     }
 
     public  Integer getAge() {
         return  age;
     }
 
     public  void  setAge(Integer age) {
         this .age = age;
     }
 
     public  Gender getGender() {
         return  gender;
     }
 
     public  void  setGender(Gender gender) {
         this .gender = gender;
     }
 
     @Override
     public  String toString() {
         return  "["  + name +  ", "  + age +  ", "  + gender +  "]" ;
     }
}
   SimpleSerial,是一個簡單的序列化程序,它先將一個Person對象保存到文件person.out中,而後再從該文件中讀出被存儲的Person對象,並打印該對象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public   class   SimpleSerial {
 
     public  static  void  main(String[] args)  throws  Exception {
         File file =  new  File( "person.out" );
 
         ObjectOutputStream oout =  new  ObjectOutputStream( new  FileOutputStream(file));
         Person person =  new  Person( "John" 101 , Gender.MALE);
         oout.writeObject(person);
         oout.close();
 
         ObjectInputStream oin =  new  ObjectInputStream( new  FileInputStream(file));
         Object newPerson = oin.readObject();  // 沒有強制轉換到Person類型
         oin.close();
         System.out.println(newPerson);
     }
}


上述程序的輸出的結果爲:
arg constructor
[John, 31, MALE]
這就是序列化。序列化的對象,他們超越了JVM的生死,不顧生他們的母親,化做永恆。

 將對象序列化存儲到文件,術語又叫「持久化」。將對象序列化發送到遠程計算機,術語又叫「數據通訊」。
 Java對序列化提供了很是方便的支持,在定義類的時候,若是想讓對象能夠被序列化,只要在類的定義上加上了」implements Serializable」便可,好比說,能夠這麼定義」public class Building implements Serializable」,其餘什麼都不要作,Java會自動的處理相關一切。Java的序列化機制至關複雜,能處理各類對象關係。

那麼序列化這種操做咱們怎麼衡量他好很差呢?主要是壓縮,速度,擴張,兼容四方面考慮。

hadoop通信格式需求

hadoop在節點間的內部通信使用的是RPC,RPC協議把消息翻譯成二進制字節流發送到遠程節點,遠程節點再經過反序列化把二進制流轉成原始的信息。RPC的序列化須要實現如下幾點:
1.壓縮,能夠起到壓縮的效果,佔用的寬帶資源要小。
2.快速,內部進程爲分佈式系統構建了高速鏈路,所以在序列化和反序列化間必須是快速的,不能讓傳輸速度成爲瓶頸。
3.可擴展的,新的服務端爲新的客戶端增長了一個參數,老客戶端照樣可使用。
4.兼容性好,能夠支持多個語言的客戶端

hadoop存儲格式需求

表面上看來序列化框架在持久化存儲方面可能須要其餘的一些特性,但事實上依然是那四點:
1.壓縮,佔用的空間更小
2.快速,能夠快速讀寫
3.可擴展,能夠以老格式讀取老數據
4.兼容性好,能夠支持多種語言的讀寫



 Java的序列化機制的缺點就是計算量開銷大,且序列化的結果體積大太,有時能達到對象大小的數倍乃至十倍。它的引用機制也會致使大文件不能分割的問題。這些缺點使得Java的序列化機制對Hadoop來講是不合適的。因而Hadoop根據本身上門的需求設計了本身的序列化機制。

Hadoop 使用本身的序列化格式Writables ,它緊湊、快速(但不容易擴展或lava 之外的語言)。因爲Writables 是Hadoop 的核心(MapReduce 程序使用它來序列化鍵/值對),因此在轉而簡要討論其餘幾個有名的序列化框架(好比Apache 的Thrift 和谷歌的Google Protocol Buffers)以前.咱們先深刻探討一下.

Hadoop經過Writable接口實現的序列化機制,不過沒有提供比較功能,因此和java中的Comparable接口合併,提供一個接口WritableComparable。

Writable接口提供兩個方法(write和readFields)。
1
2
3
4
5
package  org.apache.hadoop.io;
public  interface  Writable {
   void  write(DataOutput out)  throws  IOException;
   void  readFields(DataInput in)  throws  IOException;
}

WritableComparable實現Writable,Comparable接口

  1. package org.apache.hadoop.io;  
  2.   
  3. public interface WritableComparable <T>  extends org.apache.hadoop.io.Writable, java.lang.Comparable<T> {  
  4. }  

MapReduce在排序部分要根據key值的大小進行排序,所以類型的比較至關重要,RawComparator是Comparator的加強版。
hadoop中使用的key類型都要事先比較的接口。而且hashcode在hadoop區分keyd 時候會頻繁使用,所以確保實現hashcode的輸出在不一樣jvm同樣很重要。

  1. package org.apache.hadoop.io;  
  2.   
  3. public interface RawComparator <T>  extends java.util.Comparator<T> {  
  4.     int compare(byte[] bytes, int i, int i1, byte[] bytes1, int i2, int i3);  
  5. }  
s1 s2時開始位置,l1,l2是長度,讀取以後比較。這個就不須要反序列化。

它能夠作到,不先反序列化就能夠直接比較二進制字節流的大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public  class  TestComparator {
 
     RawComparator<IntWritable> comparator;
     IntWritable w1;
     IntWritable w2;
 
     /**
      * 得到IntWritable的comparator,並初始化兩個IntWritable
      */
     @Before
     public  void  init() {
         comparator = WritableComparator.get(IntWritable. class );
         w1 =  new  IntWritable( 163 );
         w2 =  new  IntWritable( 76 );
     }
 
     /**
      * 比較兩個對象大小
      */
     @Test
     public  void  testComparator() {
         Assert.assertEquals(comparator.compare(w1, w2) >  0 true );
     }
 
     /**
      * 序列號後進行直接比較
      *
      * @throws IOException
      */
     @Test
     public  void  testcompare()  throws  IOException {
         byte [] b1 = serialize(w1);
         byte [] b2 = serialize(w2);
         Assert.assertTrue(comparator.compare(b1,  0 , b1.length, b2,  0 , b2.length) >  0 );
     }
 
     /**
      * 將一個實現了Writable接口的對象序列化成字節流
      *
      * @param writable
      * @return
      * @throws java.io.IOException
      */
     public  static  byte [] serialize(Writable writable)  throws  IOException {
         ByteArrayOutputStream out =  new  ByteArrayOutputStream();
         DataOutputStream dataOut =  new  DataOutputStream(out);
         writable.write(dataOut);
         dataOut.close();
         return  out.toByteArray();
     }
}

額外說一句,從git上下載的hadoop項目自己自帶不少test類,你們能夠多多嘗試一下。



權威指南原文用intwritable 舉例說明hadoop如何序列化:

1
2
3
4
5
6
7
  public  static  byte [] serialize(Writable writable)  throws  IOException {
         ByteArrayOutputStream out =  new  ByteArrayOutputStream();
         DataOutputStream dataOut =  new  DataOutputStream(out);
         writable.write(dataOut);
         dataOut.close();
         return  out.toByteArray();
     }
以及反序列化:
1
2
3
4
5
6
7
8
public  static  byte [] deserialize(Writable writable,  byte [] bytes)
throws  IOException {
ByteArrayInputStream in =  new  ByteArrayInputStream(bytes);
DataInputStream dataIn =  new  DataInputStream(in);
writable.readFields(dataIn);
dataIn.close();
return  bytes;
}

這是目前全部Writeable 的Java 基本類的封裝(見表4-喲,此外還有short 和char 類型(二者都可存儲在Int W rita ble 中)。它們都有用於檢索和存儲封裝值的get () 和set ( )方撞.


類結構:


在對整數進行編碼時,在固定長度格式。intWritable 和LongWritable)和可變長度格式(VlntWritable 和vLongWritable ) 之鬧,有一個選擇.若是值足夠小(-112 和127 之問.包含這兩個值) ,可變長度格式就只用一個字節來對值進行編碼,不然,使用第一字節來表示值爲正仍是負,以及後面還有多少字節。

如何在固定長度和可變長度編碼之間進行選擇?固定長度編碼的好處在於值比較均勻地分佈在整個值空間中,就像(精心設計)的散列函數。大多數數字變量每每分布, 因此可變長度編碼每每更節省空間。可變長度編碼的另外一個好處是能夠將VintWritable 變爲VLongWritable ,由於它們的編碼其實是相同的. 所以,通過選擇可變長度的編碼方式,使空間能夠增加,而不是一開始就佔用8字節的空間。

權威指南對這些類都有一一介紹,在此再也不贅述。




Charles 2015-12-23 於P.P 



版權說明:
本文由Charles Dong原創,本人支持開源以及免費有益的傳播,反對商業化謀利。
CSDN博客:http://blog.csdn.net/mrcharles
我的站:http://blog.xingbod.cn
EMAIL:charles@xingbod.cn
相關文章
相關標籤/搜索