hadoop 源码阅读之io篇
从基础的IO包开始阅读。
IO:表示层,将各种数据编码/解码,方便于在网络上传输。
下图是一个大致的结构图,可以看出,大部分的类都是以Writable结
尾的数据结构和基本数据类型。
一、基本数据类型
Hadoop 中,并没有使用java自带的基本类型类(Integer、Float等)而是使用了自己开发的类IntWritable、FloatWritable、BooleanWritable、LongWritable、ByteWritable、BytesWritable、DoubleWritable,他们都实现了接口WritableComparable,接口定义如下:
public inte易做图ce WritableComparable<T> extends Writable, Comparable<T> {}
Comparable是java.lang包的接口。
Writable接口定义如下
public inte易做图ce Writable {
void write(DataOutput out) throws IOException;
void readFields(DataInput in) throws IOException;
Writable接口是一个序列化对象的接口,能够将数据写入流或者从流中读出。实现了之后,能够进行特定类型数据的异地传输。
除了这些基本类型的定义,还添加了VLongWritable和VIntWritable,V指的是可变长度,例如long型的1实际只需要一个字节的空间,由于是long型的,所以会占用8字节的空间,而VLongWritable中,会根据数值的大小,分配适当的空间(仅分配一个字节),达到节省空间的作用。在基本数据类型的Writable类中,readFields(DataInput in)方法是直接调用in.readLong()(以LongWritable为例),而在VLongWritable与VIntegerWritable中,readFields(DataInputin)方法是使用了静态类WritableUtils中的readVLong(in)方法。WritableUtils是一个工具类,用于提供io中的Writable类的一些静态方法。
下面分析下VLongWritable中的write和readFields方法的实现。
Write方法:
将long型的数根据所占用的字节数写入DataOutput 中,例如-112~ 127,只需要一个字节存储(-128~-113用于做标识了)。其他的数,则需要先指明该数的正负与占用的长度(在第一个字节表示),然后再按照长度存储。
第一个字符中, -113(11110001)到-120(11111000)表示正数,-121(11111000)到-128(10000000)表示负数。
后续的N个字节,表示该数字的N字节。
代码如下:
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));
}
}
readVLong方法,若第一个字节大于-112,则仅为一位数,直接返回该值;否则,则判断后面字节的位数,并读出返回。
由于对整数的操作都是以字节方式进行,故都是使用位操作进行。如
i= i << 8;
i= i | (b & 0xFF);
将i左移8位,并将其与新读入的8位数进行或运算,这样,就完成了一个字节数据的写入。
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);
}
public static int decodeVIntSize(byte value) {
if (value >= -112) {
return 1;
} else if (value < -120) {
return -119 - value;
}
return -111 - value;
}
在上述每个类初始化的时候,都会将自定义的比较器(Comparator)注册进WritableComparator的HashMap中,以供调用。
static { // register this comparator
WritableComparator.define(FloatWritable.class, new Comparator());
}
下图是这些基本数据类型的类图
二、数据结构
几个实现了Writable接口的数据结构如下
主要有4种Writable型数据结构:分别是ArrayWritable,TwoDArrayWritable,MapWritable和SortedMapWritable
1、 ArrayWritable
看到ArrayWritable的构造函数有个形式如下:
private Class<? extends Writable> valueClass;
private Writable[] values;
public ArrayWritable(Class<? extends Writable> valueClass) {
if (valueClass == null) {
throw new IllegalArgumentException("null valueClass");
}
this.valueClass = valueClass;
}
public ArrayWritable(Class<? extends Writable> valueClass, Writable[] values) {
this(valueClass);
this.values = values;
}
public ArrayWritable(String[] strings) {
this(UTF8.class, new Writable[strings.length]);
for (int i = 0; i < strings.length; i++) {
values[i] = new UTF8(strings[i]);
}
第三个构造函数中,传入的是一个字符串数组,则自动将其进行打包,使用UTF8这个类进行封装,该类实现了WritableComparable 接口。这样,能够方便的处理字符串数组。因此,除了String类型的数据,该数据结构不能够存储其他未实现Writable接口的数据。
2、 TwoDArrayWritable 是二维数组。实现不复杂,主要还是实现了Writable接口,
public Object toArray() {
int dimensions[] = {values.length, 0};
Object result = Array.newInstance(valueClass, dimensions);
for (int i = 0; i < values.length; i++) {
Object resultRow = Array.newInstance(valueClass, values[i].length);
Array.set(result, i, resultRow);
for (int j = 0; j < values[i].length; j++) {
Array.set(resultRow, j, values[i][j]);
}
}
return result;
}
该toArray方法将二维数组以对象的形式返回,使用了自带的Array里面的newInstance和set方法,而不是传统的
Array arr = newArrayList();
Arr.add(newArrayList())
的方式。自带的Array这种方式能够创建任意维度的数组(<255),并且似乎
补充:综合编程 , 其他综合 ,