最近因为实验室的云计算项目,开始学习Hadoop,有时间就记录一下自己在学习过程中的一些小收获吧。
《Hadoop权威指南》在序列化这一节有个例子程序,叫做TextPair,代码略长,就不贴上来了,它implements了WritableComparable<TextPair>,将两个Text对象打包到一起。TextPair以静态内部类的形式实现了WritableComparator,这样,不从数据流中deserialize出对象也可以对TextPair进行比较了。实现的这个Comparator中的compare方法如下:
Java代码
- @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,先看代码:
Java代码
- 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工具类中也看到了类似的方法,这个在后边会贴出来。
Java代码
- 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。
Java代码
- 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
Java代码
- 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数组中读取,所以略有不同,但是思想还是一样的。
Java代码
- 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);
- }
内容比较乱,东西也比较简单,主要目的还是给自己理清一下思路,方便以后回头能直接了然于心。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)