Hadoop中VIntWritable编码方式解析

最近因为实验室的云计算项目,开始学习Hadoop,有时间就记录一下自己在学习过程中的一些小收获吧。


《Hadoop权威指南》在序列化这一节有个例子程序,叫做TextPair,代码略长,就不贴上来了,它implements了WritableComparable<TextPair>,将两个Text对象打包到一起。TextPair以静态内部类的形式实现了WritableComparator,这样,不从数据流中deserialize出对象也可以对TextPair进行比较了。实现的这个Comparator中的compare方法如下:

 

@Override
	public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {

	    try {
		int firstL1 = WritableUtils.decodeVIntSize(b1[s1])
			+ readVInt(b1, s1);
		int firstL2 = WritableUtils.decodeVIntSize(b2[s2])
			+ readVInt(b2, s2);
		int cmp = TEXT_COMPARATOR.compare(b1, s1, firstL1, b2, s2,
			firstL2);
		if (cmp != 0) {
		    return cmp;
		}
		return TEXT_COMPARATOR.compare(b1, s1 + firstL1, l1 - firstL1,
			b2, s2 + firstL2, l2 - firstL2);
	    } catch (IOException e) {
		throw new IllegalArgumentException(e);
	    }
	}
 


  中间有两行代码比较有意思,int firstL1 = WritableUtils.decodeVIntSize(b1[s1])+ readVInt(b1, s1); 以及它之后的那一行类似的。第一个方法,decodeVIntSize,先看代码:

 

public static int decodeVIntSize(byte value) {
    if (value >= -112) {
      return 1;
    } else if (value < -120) {
      return -119 - value;
    }
    return -111 - value;
  }
 


  这个方法的功能是判断VInt的长度。这里还是稍微记一下VInt和Text,以免将来忘记。


 写道
Text使用一个采用可变长度编码方案的int在字符串编码中存储字节数。


  在decodeVIntSize方法中,现在只能是看到做了分类处理,但是具体是怎么分类还不太清楚。接着是readVInt这个方法,它是WritableComparator中的静态工具方法,我在WritableUtils工具类中也看到了类似的方法,这个在后边会贴出来。

 

public static int readVInt(byte[] bytes, int start) throws IOException {
    return (int) readVLong(bytes, start);
  }

public static long readVLong(byte[] bytes, int start) throws IOException {
    int len = bytes[start];
    if (len >= -112) {
      return len;           //1
    }
    boolean isNegative = (len < -120);    //2
    len = isNegative ? -(len + 120) : -(len + 112);   //3
    if (start+1+len>bytes.length)    //4
      throw new IOException(
                            "Not enough number of bytes for a zero-compressed integer");
    long i = 0;
    for (int idx = 0; idx < len; idx++) {    //5
      i = i << 8;
      i = i | (bytes[start+1+idx] & 0xFF);
    }
    return (isNegative ? (i ^ -1L) : i);     //6
  }
 


  readVInt引用了readVLong方法,readVLong从字节序列中尝试读取出一个采用变长编码方案的Long。因此,关键就在这个readVLong中了。

在标号1的地方,如果第一个byte>=-112,就将这个byte返回,亦即代表这就是我们想要的那个数,可以再结合 decodeVIntSize这个方法,可以确定,在VInt或VLong中,一个字节能表示的数是从0~127, -112~-1的,当数字小于了-112就会做特殊处理了,这就是开始用两个字节来存放。

往下看,在标号2的地方,又做了判断。要注意到的是,代码走到这里,表示要去的那个Int或Long已经不是用一个byte来表示的了,也就是说,第一个byte是用作长度标识了,但是从标号2这里又可以看出,第一个byte还可以用来判断这个数字是正数还是负数,从标号三可以看出,正负数的长度存储是不同的,原因从后面的代码可以看出。另外,这里分别为正数和负数做了最大8个byte长度的限制。VLongWritable的最大长度就是9,这里减一即是8.

在到达标号4时候,这个数字的长度已经计算出来了,剩下的就是解码出数字了,但是,如果解码出数字所需的byte数目超出了这个bytes数组的boundary,就只能抛出异常了。

从标号5开始对剩下的数据进行解码,对java的byte处理有点了解的都应该看得懂,只做一个对自己的提醒,使用bytes[start+1+idx] & 0xFF是为了确保获得正确的byte值,以防java帮你自动转换。如果你的byte的most significant bit是1,也就是说明这个byte是表示的负数,如果将这个负数与一个Long进行位的或操作,这个byte的所有高于第9位的所有位都会被填充上1,这可不是我们想要的。

标号6,之前做的判断这里用到了,如果是负数,就对它取-1,也就是11111111...(64个1,-1的二进制表示),的反,这么做的原因是在写入这个VInt或VLong的时候做了相反的操作,具体为什么这样我也高步太懂,但是这么做所有的数据byte(就是除了第一个byte)的most significant bit都是0.


以上两个方法 decodeVIntSize + readVInt 就完美地解析出了一个完整Text的长度。


在WritableComparator中并没有写VIntWritable或VLongWritable的方法,这时候找到VIntWritable。

 

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

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


  嗯,大哥来了,WritableUtils,代码参上。同样,writeVInt引用了writeVLong

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'
      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));
    }
  }
 


  如果已经看到这里,我想也没有太多好说的了,先判断可不可以用一个byte表示,然后分为正负数处理,之后再计算长度,最后将数字编码成byte写入流。其实我写到这里才觉得,如果先找到write方法,什么都一目了然了,但是都写了这么多了,哎。。。就将就这样吧。

最后把大哥的readVLong也贴出来,这个方法是直接从stream中而不是从byte数组中读取,所以略有不同,但是思想还是一样的。

 

public static long readVLong(DataInput stream) throws IOException {
    byte firstByte = stream.readByte();
    int len = decodeVIntSize(firstByte);    //这个函数应该还是比较熟悉了吧
    if (len == 1) {
      return firstByte;
    }
    long i = 0;
    for (int idx = 0; idx < len-1; idx++) {
      byte b = stream.readByte();
      i = i << 8;
      i = i | (b & 0xFF);
    }
    return (isNegativeVInt(firstByte) ? (i ^ -1L) : i);
  }
 

 

内容比较乱,东西也比较简单,主要目的还是给自己理清一下思路,方便以后回头能直接了然于心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值