序列化(serialization),是指将结构化对象转换为字节流以便网络传输或存储至磁盘,反序列化(deserialization)就是把字节流转换为结构化对象的过程.
Hadoop为什么需要这个过程,因为在大型集群中,网络带宽资源是非常宝贵的,能省则省.
在Hadoop中,系统中的多个节点上进程间的通信是通过"远程过程调用"(RPC)是限定.RPC协议将消息序列化成二进制流之后发送到远程节点,远程节点接着将二进制流反序列化为原始消息.
说到这里大家可能会想到,Java中也有序列化接口,而且Hadoop就是用Java实现的,为什么不直接使用Java的序列化接口 java.io.Serializable呢,Hadoop之父,Doug Cutting是这么说的:"因为它看起来太复杂,然而我们需要有一个至精至简的机制,可以精确控制对象的读和写,这个机制将是Hadoop的核心."
Hadoop使用的是自己的序列化格式Writable,它绝对紧凑,速度快,但不太容易用Java以外的语言进行扩展或使用.
Writable是一个接口,Hadoop的序列化都是从这个接口扩展而来的提供了两个方法:,write()用来写入DataOutput二进制流,readFields()用来从DataInput二进制流读取状态:
public interface Writable {
void write(DataOutput out) throws IOException;
void readFields(DataInput in) throws IOException;
}
接下来看看Hadoop中具体的序列化类,首先来看看IntWritable,对应到Java就是int类型.我们要做的事情是,先给它序列化看看变成了什么内容,再反序列化回去看看能不能恢复原本的内容.
首先写一个工具类:
import org.apache.hadoop.io.*;
import java.io.*;
import java.util.Arrays;
/**
* 工具类
*/
class Utils {
/**
* 将传入的writable对象序列化为字节数组.
*/
static byte[] serialize(Writable writable) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
writable.write(dataOut);
dataOut.close();
return out.toByteArray();
}
/**
* 将字节数组反序列化为writable对象.
*/
static byte[] deserialize(Writable writable, byte[] bytes) throws IOException{
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
DataInputStream dataIn = new DataInputStream(in);
writable.readFields(dataIn);
dataIn.close();
return bytes;
}
/**
* 输出一个字节数组.
*/
static void printArray(byte[] bytes) {
System.out.println(Arrays.toString(bytes));
}
/**
* 比较两个参数是否一样,如果为false,就分别输出两个参数.
*/
static void assertThat(Object o1, Object o2){
if (o1 != null && o2 != null) {
if (o1.equals(o2)) {
System.out.println(true);
} else {
System.out.print(false);
System.out.println(",(" + o1.toString() + "," + o2.toString() + ")");
}
}
}
}
序列化与反序列化方法参考的是<权威指南>的写法,核心是一个字节数组.
定长类型
首先创建一个IntWritable对象, 然后利用工具类的方法将其序列化并输出:
IntWritable iw1 = new IntWritable(100);
byte[] b1 = Utils.serialize(iw1);
Utils.printArray(b1);
得到的结果为:
[0, 0, 0, 100]
100比较容易知道是什么意思,但是前面3个0是啥意思啊?我们接着用更大的值来看看:
IntWritable iw1 = new IntWritable(100);
IntWritable iw2 = new IntWritable(127);
IntWritable iw3 = new IntWritable(128);
IntWritable iw4 = new IntWritable(255);
IntWritable iw5 = new IntWritable(256);
IntWritable iw6 = new IntWritable(512);
IntWritable iw7 = new IntWritable(65536);
//65536*256 = 16777216
IntWritable iw8 = new IntWritable(16777216);
//为了优化阅读效果,后面就不再写这条语句了.
Utils.printArray(Utils.serialize(iw1));
再次运行,看到结果的时候其实应该就明白了:
[0, 0, 0, 100]
[0, 0, 0, 127]
[0, 0, 0, -128]
[0, 0, 0, -1]
[0, 0, 1, 0]
[0, 0, 2, 0]
[0, 1, 0, 0]
[1, 0, 0, 0]
127对应[0, 0, 0, 127],而128却对应[0, 0, 0, -128],想到了什么?对了,封闭式运算:
Byte.MAX_VALUE + 1 == Byte.MIN_VALUE
byte的取值范围是127~-128.再来看看255与256,255对应的是[0, 0, 0, -1],256对应的是[0, 0, 1, 0],可以发现,在256的时候,第3个数字变成了1.再看看512,输出结果为[0, 0, 2, 0],65536的输出结果为[0, 1, 0, 0],16777216(=65535*256)的输出结果为[1, 0, 0, 0].怎么解释?
我们知道二进制运算时,比如一个二进制数是10010111,对应的十进制是151,一个运算方法是:
1*2^7 + 0*2^6 + 0*2^5+ 1*2^4+ 0*2^3+ 1*2^2+ 1*2^1+ 1*2^0
如果把刚才的序列化结果理解为一个"256进制运算",是不是一切就可以解释的通?只不过要把负的byte类型数值转换为正的.比如我们来解释一下512,65536与1677216:
512
[ 0 , 0 , 2 , 0 ]
0*256^3 + 0*256^2 + 2*256^1 + 0^256^0
65536:
[ 0 , 1 , 0 , 0 ]
0*256^3 + 1*256^2 + 0*256^1 + 0^256^0
1677216
[ 1 , 0 , 0 , 0 ]
1*256^3 + 0*256^2 + 0*256^1 + 0^256^0
结果完全正确,那么负数呢?
IntWritable iw9 = new IntWritable(-256);
IntWritable iw10 = new IntWritable(-512);
IntWritable iw11 = new IntWritable(-65536);
IntWritable iw12 = new IntWritable(-16777216);
IntWritable iw13 = new IntWritable(-127);
IntWritable iw14 = new IntWritable(-129);
运行结果:
[-1, -1, -1, 0]
[-1, -1, -2, 0]
[-1, -1, 0, 0]
[-1, 0, 0, 0]
[-1, -1, -1, -127]
[-1, -1, -1, 127]
思路是一样的,只不过是反过来的封闭式运算,且计算的初始值从0变为-1:
Byte.MIN_VALUE - 1 == Byte.MAX_VALUE
还有LongWritable,具体原理是跟IntWritable一样的,只不过数组元素数扩充为8,IntWritable是4个数字代表int是4字节长度的,而LongWritable是8个数字也就代表了long是8字节长度.这里不做展示.
当然还有浮点型FloatWritable与DoubleWritable,具体的原理大家来分析一下,我不是太理解:
FloatWritable fw1 = new FloatWritable(0.5F);
FloatWritable fw2 = new FloatWritable(0.25F);
FloatWritable fw3 = new FloatWritable(0.125F);
DoubleWritable dw1 = new DoubleWritable(0.5);
DoubleWritable dw2 = new DoubleWritable(0.25);
DoubleWritable dw3 = new DoubleWritable(0.125);
运行结果:
[63, 0, 0, 0]
[62, -128, 0, 0]
[62, 0, 0, 0]
[63, -32, 0, 0, 0, 0, 0, 0]
[63, -48, 0, 0, 0, 0, 0, 0]
[63, -64, 0, 0, 0, 0, 0, 0]
变长类型
Hadoop中还有一种变长类型:VIntWritable,VLongWritable.
对于可变与不可变的具体区别,在于序列化后的数组元素数量:
类型 | 序列化后字节数组元素数 |
IntWritable | 4 |
VIntWritable | 1~5 |
LongWritable | 8 |
VLongWritable | 1~9 |
来看几个例子,看看最大元素数多出来的一个是什么.
VIntWritable viw1 = new VIntWritable(100);
VIntWritable viw2 = new VIntWritable(127);
VIntWritable viw3 = new VIntWritable(128);
VIntWritable viw4 = new VIntWritable(256);
VIntWritable viw5 = new VIntWritable(512);
VIntWritable viw6 = new VIntWritable(65536);
VIntWritable viw7 = new VIntWritable(16777216);
VIntWritable viw8 = new VIntWritable(-100);
VIntWritable viw9 = new VIntWritable(-127);
VIntWritable viw10 = new VIntWritable(-128);
VIntWritable viw11 = new VIntWritable(-256);
VIntWritable viw12 = new VIntWritable(-512);
VIntWritable viw13 = new VIntWritable(-65536);
VIntWritable viw14 = new VIntWritable(-16777216);
运行结果:
[100]
[127]
[-113, -128]
[-114, 1, 0]
[-114, 2, 0]
[-115, 1, 0, 0]
[-116, 1, 0, 0, 0]
[-100]
[-121, 126]
[-121, 127]
[-121, -1]
[-122, 1, -1]
[-122, -1, -1]
[-123, -1, -1, -1]
可以看到,正数情况下前面的0都不见了,只保留了后面有效的数字;负数情况下,后面的0都不见了,只保留了前面的有效数字.其他都是一样的,但是负数的情况还有不一样,下面来看看:
for (int i = 0; i < Byte.MAX_VALUE * 2; i++) {
byte[] b = Utils.serialize(new VIntWritable(- i -110));
System.out.println("(" + ( -i - 110 ) + "," + Utils.arrayToString(b) + ")");
}
这里把输出做了一点处理,格式是:(数字(-i - 110),[字节数组]) ,下面来看看部分输出结果(注意箭头指的位置):
(-110,[-110])
(-111,[-111])
(-112,[-112]) ←
(-113,[-121, 112]) ←
(-114,[-121, 113])
(-115,[-121, 114])
.
.
.
(-127,[-121, 126])
(-128,[-121, 127])
(-129,[-121, -128])
.
.
.
(-254,[-121, -3])
(-255,[-121, -2])
(-256,[-121, -1])
(-257,[-122, 1, 0])
(-258,[-122, 1, 1])
(-259,[-122, 1, 2])
(-260,[-122, 1, 3])
.
.
.
经过测试,只有-112~-113~-128会出现这种情况,具体原理不明...
第一个数字在正数情况下是在-113~-116,每多一个数字就-1,负数经测试范围在-121~-123之间,多一个数字就-1,不知道代表什么意思...猜测第一个数字的最大值-113可能与-112到-113的变化有关系.当然也只是猜测.
下一篇说说非数值类型的序列化与反序列化.