hadoop中典型Writable類詳解


本文地址:http://www.cnblogs.com/archimedes/p/hadoop-writable.html,轉載請注明源地址。

Hadoop將很多Writable類歸入org.apache.hadoop.io包中,在這些類中,比較重要的有Java基本類、Text、Writable集合、ObjectWritable等,重點介紹Java基本類和ObjectWritable的實現。

1. Java基本類型的Writable封裝

目前Java基本類型對應的Writable封裝如下表所示。所有這些Writable類都繼承自WritableComparable。也就是說,它們是可比較的。同時,它們都有get()和set()方法,用於獲得和設置封裝的值。

Java基本類型對應的Writable封裝

Java基本類型 Writable 序列化后長度
布爾型(boolean) BooleanWritable 1
字節型(byte) ByteWritable 1
整型(int)

IntWritable

VIntWritable

4

1~5

浮點型(float) FloatWritable 4
長整型(long)

LongWritable

VLongWritable

8

1~9

雙精度浮點型(double) DoubleWritable 8

在表中,對整型(int和long)進行編碼的時候,有固定長度格式(IntWritable和LongWritable)和可變長度格式(VIntWritable和VLongWritable)兩種選擇。固定長度格式的整型,序列化后的數據是定長的,而可變長度格式則使用一種比較靈活的編碼方式,對於數值比較小的整型,它們往往比較節省空間。同時,由於VIntWritable和VLongWritable的編碼規則是一樣的,所以VIntWritable的輸出可以用VLongWritable讀入。下面以VIntWritable為例,說明Writable的Java基本類封裝實現。代碼如下:

public class VIntWritable implements WritableComparable {
   private int value;
   ……
   // 設置VIntWritable的值
   public void set(int value) { this.value = value; }

   // 獲取VIntWritable的值
   public int get() { return value; }

   public void readFields(DataInput in) throws IOException {
      value = WritableUtils.readVInt(in);
   }

   public void write(DataOutput out) throws IOException {
      WritableUtils.writeVInt(out, value);
   }
   ……
}

首先,每個Java基本類型的Writable封裝,其類的內部都包含一個對應基本類型的成員變量value,get()和set()方法就是用來對該變量進行取值/賦值操作的。而Writable接口要求的readFields()和write()方法,VIntWritable則是通過調用Writable工具類中提供的readVInt()和writeVInt()讀/寫數據。方法readVInt()和writeVInt()的實現也只是簡單調用了readVLong()和writeVLong(),所以,通過writeVInt()寫的數據自然可以通過readVLong()讀入。

writeVLong ()方法實現了對整型數值的變長編碼,它的編碼規則如下:

如果輸入的整數大於或等於–112同時小於或等於127,那么編碼需要1字節;否則,序列化結果的第一個字節,保存了輸入整數的符號和后續編碼的字節數。符號和后續字節數依據下面的編碼規則(又一個規則):

如果是正數,則編碼值范圍落在–113和–120間(閉區間),后續字節數可以通過–(v+112)計算。

如果是負數,則編碼值范圍落在–121和–128間(閉區間),后續字節數可以通過–(v+120)計算。

后續編碼將高位在前,寫入輸入的整數(除去前面全0字節)。代碼如下:

public final class WritableUtils  {
   public stati cvoid writeVInt(DataOutput stream, int i) throws IOException
   {
      writeVLong(stream, i);
   }
   /**
    * @param stream保存系列化結果輸出流
    * @param i 被序列化的整數
    * @throws java.io.IOException
    */
   public static void writeVLong(DataOutput stream, long i) throws……
   {
      //處於[-112, 127]的整數
      if (i >= -112 && i <= 127) {
         stream.writeByte((byte)i);
         return;
      }
      //計算情況2的第一個字節
      int len = -112;
      if (i < 0) {
         i ^= -1L;
         len = -120;
      }
      long tmp = i;
      while (tmp != 0) {
         tmp = tmp >> 8;
         len--;
      }
      stream.writeByte((byte)len);
      len = (len < -120) ? -(len + 120) : -(len + 112);
      //輸出后續字節
      for (int idx = len; idx != 0; idx--) {
         int shiftbits = (idx - 1) * 8;
         long mask = 0xFFL << shiftbits;
         stream.writeByte((byte)((i & mask) >> shiftbits));
      }
   }
}

2. ObjectWritable類的實現

針對Java基本類型、字符串、枚舉、Writable、空值、Writable的其他子類,ObjectWritable提供了一個封裝,適用於字段需要使用多種類型。ObjectWritable可應用於Hadoop遠程過程調用中參數的序列化和反序列化;ObjectWritable的另一個典型應用是在需要序列化不同類型的對象到某一個字段,如在一個SequenceFile的值中保存不同類型的對象(如LongWritable值或Text值)時,可以將該值聲明為ObjectWritable。

ObjectWritable的實現比較冗長,需要根據可能被封裝在ObjectWritable中的各種對象進行不同的處理。ObjectWritable有三個成員變量,包括被封裝的對象實例instance、該對象運行時類的Class對象和Configuration對象。

ObjectWritable的write方法調用的是靜態方法ObjectWritable.writeObject(),該方法可以往DataOutput接口中寫入各種Java對象。

writeObject()方法先輸出對象的類名(通過對象對應的Class 對象的getName()方法獲得),然后根據傳入對象的類型,分情況序列化對象到輸出流中,也就是說,對象通過該方法輸出對象的類名,對象序列化結果對到輸出流中。在ObjectWritable.writeObject()的邏輯中,需要分別處理null、Java數組、字符串String、Java基本類型、枚舉和Writable的子類6種情況,由於類的繼承,處理Writable時,序列化的結果包含對象類名,對象實際類名和對象序列化結果三部分。

為什么需要對象實際類名呢?根據Java的單根繼承規則,ObjectWritable中傳入的declaredClass,可以是傳入instance對象對應的類的類對象,也可以是instance對象的父類的類對象。但是,在序列化和反序列化的時候,往往不能使用父類的序列化方法(如write方法)來序列化子類對象,所以,在序列化結果中必須記住對象實際類名。相關代碼如下:

public class ObjectWritable implements Writable, Configurable {
   private Class declaredClass;//保存於ObjectWritable的對象對應的類對象
   private Object instance;//被保留的對象
   private Configuration conf;

   public ObjectWritable() {}

   public ObjectWritable(Object instance) {
      set(instance);
   }

   public ObjectWritable(Class declaredClass, Object instance) {
      this.declaredClass = declaredClass;
      this.instance = instance;
   }           
   ……
   public void readFields(DataInput in) throws IOException {
      readObject(in, this, this.conf);
   }

   public void write(DataOutput out) throws IOException {
      writeObject(out, instance, declaredClass, conf);
   }
   ……
   public static void writeObject(DataOutput out, Object instance,
         Class declaredClass,Configuration conf) throws……{

      if (instance == null) {//
         instance = new NullInstance(declaredClass, conf);
         declaredClass = Writable.class;
      }

      // 寫出declaredClass的規范名
      UTF8.writeString(out, declaredClass.getName());

      if (declaredClass.isArray()) {//數組
         ……
      } else if (declaredClass == String.class) {//字符串
         ……
      } else if (declaredClass.isPrimitive()) {//基本類型
         if (declaredClass == Boolean.TYPE) {        //boolean
            out.writeBoolean(((Boolean)instance).booleanValue());
         } else if (declaredClass == Character.TYPE) { //char
            ……
         }
      } else if (declaredClass.isEnum()) {//枚舉類型
         ……
      } else if (Writable.class.isAssignableFrom(declaredClass)) {
         //Writable的子類
         UTF8.writeString(out, instance.getClass().getName());
         ((Writable)instance).write(out);
      } else {
         ……
   }

   public static Object readObject(DataInput in,
                  ObjectWritable objectWritable, Configuration conf){
      ……
      Class instanceClass = null;
      ……
      Writable writable = WritableFactories.newInstance(instanceClass,
            conf);
      writable.readFields(in);
      instance = writable;
      ……
   }
}

和輸出對應,ObjectWritable的readFields()方法調用的是靜態方法ObjectWritable.readObject(),該方法的實現和writeObject()類似,唯一值得研究的是Writable對象處理部分,readObject()方法依賴於WritableFactories類。WritableFactories類允許非公有的Writable子類定義一個對象工廠,由該工廠創建Writable對象,如在上面的readObject()代碼中,通過WritableFactories的靜態方法newInstance(),可以創建類型為instanceClass的Writable子對象。相關代碼如下:

public class WritableFactories {
   //保存了類型和WritableFactory工廠的對應關系
   private static final HashMap<Class, WritableFactory>CLASS_TO_FACTORY
      = new HashMap<Class, WritableFactory>();
   ……
   public static Writable newInstance(Class<? extends Writable> c,
                                      Configuration conf) {
      WritableFactory factory = WritableFactories.getFactory(c);
      if (factory != null) {
         Writable result = factory.newInstance();
         if (result instanceof Configurable) {
            ((Configurable) result).setConf(conf);
         }
         return result;
      } else {
         //采用傳統的反射工具ReflectionUtils,創建對象
         return ReflectionUtils.newInstance(c, conf);
      }
   }
}

WritableFactories.newInstance()方法根據輸入的類型查找對應的WritableFactory工廠對象,然后調用該對象的newInstance()創建對象,如果該對象是可配置的,newInstance()還會通過對象的setConf()方法配置對象。

WritableFactories提供注冊機制,使得這些Writable子類可以將該工廠登記到WritableFactories的靜態成員變量CLASS_TO_FACTORY中。下面是一個典型的WritableFactory工廠實現,來自於HDFS的數據塊Block。其中,WritableFactories.setFactory()需要兩個參數,分別是注冊類對應的類對象和能夠構造注冊類的WritableFactory接口的實現,在下面的代碼里,WritableFactory的實現是一個匿名類,其newInstance()方法會創建一個新的Block對象。

public class Block implements Writable, Comparable<Block> {
   static {
      WritableFactories.setFactory
         (Block.class,//類對象
         new WritableFactory() {//對應類的WritableFactory實現
            public Writable newInstance() { return new Block(); }
         });
   }           
   ……
}

ObjectWritable作為一種通用機制,相當浪費資源,它需要為每一個輸出寫入封裝類型的名字。如果類型的數量不是很多,而且可以事先知道,則可以使用一個靜態類型數組來提高效率,並使用數組索引作為類型的序列化引用。GenericWritable就是因為這個目的被引入org.apache.hadoop.io包中。


免責聲明!

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



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