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个字节。

时间: 2024-10-26 02:55:52

Hadoop的一个变长long编码剖析的相关文章

Java语法糖初探(三)--变长参数

变长参数概念 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用.形如 function(T -args).但是需要明确的一点是,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组. 看下面的例子: 12345678910111213 public class VariVargs { public static void main(String []args) { tes

java 变长參数使用原则

1.java变长參数用...表示,如Print(String... args){  ... }; 2.假设一个调用既匹配一个固定參数方法.又匹配一个变长參数方法,则优先匹配固定參数的方法 3.假设一个调用能匹配两个及以上的变长參数方法,则出现错误--这事实上表示方法设计有问题,编译器会提示The method is ambiguous 4.方法仅仅能有一个变长參数,且必须放在參数列表的最后一个

java 变长参数使用原则

1.java变长参数用...表示,如Print(String... args){  ... }; 2.如果一个调用既匹配一个固定参数方法,又匹配一个变长参数方法,则优先匹配固定参数的方法 3.如果一个调用能匹配两个及以上的变长参数方法,则出现错误--这其实表示方法设计有问题,编译器会提示The method is ambiguous 4.方法只能有一个变长参数,且必须放在参数列表的最后一个java 变长参数使用原则,布布扣,bubuko.com

java变长参数

从java5开始提供了变长参数,可以把变长参数当作数据使用 可变长参数方法的定义 使用...表示可变长参数,例如 print(String... args){ ... } 在具有可变长参数的方法中可以把参数当成数组使用,例如可以循环输出所有的参数值. print(String... args){ for(String temp:args) System.out.println(temp); } 可变长参数的方法的调用 调用的时候可以给出任意多个参数,例如: print("hello")

C99新增内容之变长数组(VLA)

我们在使用多维数组是有一点,任何情况下只能省略第一维的长度.比如在函数中要传一个数组时,数组的行可以在函数调用时传递,当属数组的列却只能在能被预置在函数内部.看下面一个例子: #define COLS 4 int sum2d(int ar[][COLS],int rows) { int r; int c; int tot=0; for(r=0;r<rows;r++) for(c=0;c<COLS;c++) tot+=ar[r][c]; return tot; } 现在假设定义了如下数组: in

变长参数

先有如下需求:返回实参中的最大者.但由于实参的个数不确定,难以采用重载的办法. 解决:在定义方法时将形参的格式定义如下: int getMax(int first,int...varArgs){ for(int i:varAgus) .... } 调用: System.out.println("max(1)="+demo.getMax(1)+" ");//1 System.out.println("max(2)="+demo.getMax(2,1

如何定义变长参数个数的函数

定义参数个数不确定的函数,需用到头文件stdarg.h,该头文件是专门为变长参数函数所用. 参数变长函数的声明:void function(int intVal, ...),当然参数类型可以为double或其他,返回类型也可以自己修改. 方法: 先用头文件stdarg.h中的宏va_list定义一个指向参数的指针ap,va_list ap: 再用宏va_start初始化指针ap,va_start(ap,知名变量名intVal): 再用va_arg使指针ap指向下一参数,并且取出该参数,va_ar

变长数组(variable-length array,VLA)

处理二维数组的函数有一处可能不太容易理解,数组的行可以在函数调用的时候传递,但是数组的列却只能被预置在函数内部.例如下面这样的定义: 1 #define COLS 4 2 int sum3d(int ar[][COLS], int rows) 3 { 4 int r, c, tot; 5 tot = 0; 6 7 for(r = 0; r < rows; r++) 8 for(c = 0; c < COLS; c++) 9 tot += ar[r][c]; 10 return tot; 11

设计表的时候,对变长字段长度选择的一点思考

不管是在MSSQL还是MySQL或者Oracle,变长字段的长度衡量都是要经常面对的.对于一个变长的字段,在满足业务的情况下(其实所谓的满足业务是一个比较模糊的东西),到底是选择varchar(50)还是varchar(200)亦或是varchar(500)?对于保守型选择,往往是选择一个较大的长度,比如varchar(500)要比varchar(50)更具有兼容性,因为是变长字段的原因,存储空间也一样.这样的选择并不能说就不好,看站在哪个角度来看问题.那么,相对于varchar(50),var