Hadoop的一個變長long編碼剖析

    Hadoop對於long、int (化成long進行編碼)的編碼設計了自己的一套編碼方式,這是一個zero-compressed encoded的變長編碼方式,有利於大大壓縮冗餘數據。具體算法其實很簡單,具體來說有如下幾點:

1、對於-112 <= i <= 127的整數,只用1個字節byte來表示;如果超過上述範圍時,編碼第一個字節則會用來表示i的總字節數,後面則跟着 i 的字節;

2、如果i大於0,則編碼的第一個字節 b 範圍在-113和-120之間,則 i 會有 (-112 - b)個字節,所以可以表示有1-8個字節;

3、如果i小於0,則編碼第一個字節 b 範圍在 -121 和 -128之間,則 i 會有 (-120 - b)個字節,同樣也可以表示有1-8個字節。(Hadoop的實現裏,當i爲負數被編碼的是 i 補碼)。

    算法看上去比較容易理解,具體要點就是利用第一個字節表示 i 的長度,以及 i 的符號,不過其實,如果深入源碼後,發現Hadoop的實現有點小巧妙的地方,我們先看代碼的實現:

    首先是變長long的編碼:

 public static void writeVLong(DataOutput stream, long i) throws IOException {
    if (i >= -112 && i <= 127) {
      stream.writeByte((byte)i);
      return;
    }
      
    int len = -112;
    if (i < 0) {
      i ^= -1L; // take one's complement'  //關鍵部分! 替換做法是 i = -i;
      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));
    }
  }

    爲了方便,我這裏也貼上自己稍微簡化了Hadoop實現的解碼變長long的實現:

    public static long readVLong(DataInputStream input) throws IOException {
        byte firstByte = input.readByte();

        int len = -112;
        boolean isNegative = false;
        if (firstByte >= -112 && firstByte <= 127) {
            return firstByte;
        } else if (firstByte <= -121) {
            len = -120;
            isNegative = true;
        }

        len = len - firstByte;

        long res = 0;
        for (int i = 0; i < len; ++i) {
            res <<= 8;
            byte b = input.readByte();
            res = (b & 0xFF) | res;
        }

        //如果編碼是i = -i; 則這裏是return isNegative ? (-res) : res;
        return isNegative ? (res ^ -1L) : res;
    }
    算法的具體實現部分,參照之前概括的描述很容易瞭解大致框架,但有一個很關鍵的部分,就是在添加了註釋的編碼和解碼的部分,對於算法第3個條件裏,如果 i 爲負數的時候,Hadoop的默認實現裏會把 i 進行補碼運算,然後再繼續執行編碼,而因此,在解碼的時候,最後部分也要重新取一個補碼操作。

算法思想分析

    爲什麼要這樣呢?其實分析一下整個算法的原理。首先如果我們簡單的把第一個字節表示 i 的字節數,不分爲正、負兩個部分來額外表示符號的話,這樣會出現一個問題:那就是會沒辦法通過變長編碼簡單實現正負判斷,舉個簡單的例子,對於 i = 128和 i = -128,這兩個數的編碼對於1個字節來說,都是0x80!爲什麼會這樣呢?如果想到負數的二進制編碼是正數取反後加1(加1是爲了避免直接取反對0進行兩次編碼,這樣負數能夠多表示1個數),因此,對於給定的字節,負數總是會比正數多表示1個數,對於1個字節,能表示-128~127。因此對於 i = 128的時候,沒辦法分辨出正負,必須要靠第一個字節添加符號信息。

    當給第一個字節多分8個數出來表示符號的時候,爲了要計算 i 的位數,如果 i 爲負數的時候,i 的高位則全爲1, 因此必須要對 i 爲負數的情況取反,然後再不斷循環計算 i 的長度,但事實上,我們同樣也可以對 i 取反後加1,也就是對 i = -i;轉爲絕對值,而事實上,經過本人的測試,無論是取反或者是做絕對值操作,兩者均可以正常進行編碼解碼,但事實上,取反有一個好處,對於i = -256的時候,如果將 i 取反,則會編碼輸出的兩個字節爲:-121,-1。如果將 i 取絕對值,則編碼輸出的兩個字節爲:-122,1,0。可見,對於這種的時候,取反能夠比取絕對值少用1個字節。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章