hadoop为序列化提供了优化,类型的比较对M/R而言至关重要,Key和Key的比较也是在排序阶段完成的,hadoop提供了原生的比较器接口RawComparator<T>用于序列化字节间的比较,该接口允许其实现直接比较数据流中的记录,无需反序列化为对象,RawComparator是一个原生的优化接口类,它只是简单的提供了用于数据流中简单的数据对比方法,从而提供优化:
1 |
public interface RawComparator<T> extends Comparator<T> { |
3 |
public int compare( byte [] b1, int s1, int l1, byte [] b2, int s2, int l2); |
该类并非被多数的衍生类所实现,其具体的子类为WritableComparator,多数情况下是作为实现Writable接口的类的内置类,提供序列化字节的比较。下面是RawComparator接口内置类的实现类图:
首先,我们看RawComparator的具体实现类WritableComparator:
WritableComparator类类似于一个注册表,里面记录了所有Comparator类的集合。
Comparators成员用一张Hash表记录Key=Class,value=WritableComprator的注册信息.
WritableComparator主要提供了两个功能
1.提供了对原始compare()方法的一个默认实现
默认实现是先反序列化为对像再通过对像比较(有开销的问题)
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
try {
buffer.reset(b1, s1, l1);// parse key1
key1.readFields(buffer);
buffer.reset(b2, s2, l2);// parse key2
key2.readFields(buffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
return compare(key1, key2);// compare them
}
而对应的基础数据类型的compare()的实现却巧妙的利用了特定类型的泛化:(利用了writableComparable的compareTo方法)
public int compare(WritableComparable a, WritableComparable b) {
return a.compareTo(b);
}
例如IntWritable实例是调用了IntWritable里的compareTo方法
public int compareTo(Object o) {
int thisValue = this.value;
int thatValue = ((IntWritable)o).value;
return (thisValue<thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
}
2.充当RawComparable实例的工厂,以注册Writable的实现
例如,为了获取IntWritable的Comparator,可以直接调用其get方法。
WritableComparator:
关键代码:
代码1:registry注册器
----------------------------------------------------------------
// registry注册器:记载了WritableComparator类的集合
privatestaticHashMap<Class,WritableComparator>comparators=
newHashMap<Class, WritableComparator>();
代码2:获取WritableComparator实例
说明:hashMap作为容器类线程不安全,故需要synchronized同步,get方法根据key=Class返回对应的WritableComparator,若返回的是空值NUll,则调用protected Constructor进行构造,而其两个protected的构造函数实则是调用了newKey()方法进行NewInstance
1 |
public static synchronized WritableComparator get(Class<? extends WritableComparable> c) { |
2 |
WritableComparator comparator = comparators.get(c); |
4 |
comparator = new WritableComparator(c, true ); |
代码3:构造方法
---------------------------------------------------------------
01 |
new WritableComparator(c, true ) |
03 |
WritableComparator的构造函数源码如下: |
11 |
private final Class<? extends WritableComparable> keyClass; |
13 |
private final WritableComparable key1; |
15 |
private final WritableComparable key2; |
17 |
private final DataInputBuffer buffer; |
19 |
protected WritableComparator(Class<? extends WritableComparable> keyClass, |
21 |
boolean createInstances) { |
23 |
this .keyClass = keyClass; |
31 |
buffer = new DataInputBuffer(); |
上述的keyClass,key1,key2,buffer是记录HashMap对应的key值,用于WritableComparator的构造函数,但由其构造函数中我们可以看出WritableComparator根据Boolean createInstance来判断是否实例化key1,key2和buffer,而key1,key2作为实现了WritableComparable接口的标识,在WritableComparator的构造函数里面通过newKey()的方法去实例化实现WritableComparable接口的一个对象,下面是newKey()的源码,通过hadoop自身的反射去实例化了一个WritableComparable接口对象。
1 |
<b> public WritableComparable newKey() { |
3 |
return ReflectionUtils.newInstance(keyClass, null ); |
代码4:Compare()方法
---------------------------------------------------------------------
1.publicintcompare(Object a, Object b);
2.publicintcompare(WritableComparablea,WritableComparableb);
3.publicintcompare(byte[] b1,ints1,intl1,byte[] b2,ints2,intl2);
三个compare()重载方法中,compare(Object a, Object b)利用子类塑形为WritableComparable而调用了第2个compare方法,而第2个Compare()方法则调用了Writable.compaerTo();最后一个compare(byte[] b1,ints1,intl1,byte[] b2,ints2,intl2)方法源码如下:
01 |
public int compare( byte [] b1, int s1, int l1, byte [] b2, int s2, int l2) { |
05 |
buffer.reset(b1, s1, l1); |
07 |
key1.readFields(buffer); |
11 |
buffer.reset(b2, s2, l2); |
13 |
key2.readFields(buffer); |
17 |
} catch (IOException e) { |
19 |
throw new RuntimeException(e); |
25 |
return compare(key1, key2); |
Compare方法的一个缺省实现方式,根据接口key1,ke2反序列化为对象再进行比较。
利用Buffer为桥接中介,把字节数组存储为buffer后,调用key1(WritableComparable)的反序列化方法,再来比较key1,ke2,由此处可以看出,该compare方法是将要比较的二进制流反序列化为对象,再调用方法第2个重载方法进行比较。
代码5:方法define方法
该方法用于注册WritebaleComparaor对象到注册表中,注意同时该方法也需要同步,代码如下:
1 |
public static synchronized void define(Class c, |
2 |
WritableComparator comparator) { |
3 |
comparators.put(c, comparator); |
代码5:余下诸如readInt的静态方法
---------------------------------------------------------------------
这些方法用于实现WritableComparable的各种实例,例如IntWritable实例:内部类Comparator类需要根据自己的IntWritable类型重载WritableComparator里面的compare()方法,可以说WritableComparator里面的compare()方法只是提供了一个缺省的实现,而真正的compare()方法实现需要根据自己的类型如IntWritable进行重载,所以WritableComparator方法中的那些readInt..等方法只是底层的封装的一个实现,方便内部Comparator进行调用而已。
下面我们着重看下BooleanWritable类的内置RawCompartor<T>的实现过程:
02 |
* A Comparator optimized for BooleanWritable. |
04 |
public static class Comparator extends WritableComparator { |
06 |
super (BooleanWritable. class ); |
09 |
public int compare( byte [] b1, int s1, int l1, |
10 |
byte [] b2, int s2, int l2) { |
11 |
boolean a = (readInt(b1, s1) == 1 ) ? true : false ; |
12 |
boolean b = (readInt(b2, s2) == 1 ) ? true : false ; |
13 |
return ((a == b) ? 0 : (a == false ) ? - 1 : 1 ); |
18 |
WritableComparator.define(BooleanWritable. class , new Comparator()); |
总结:
hadoop 类似于Java的类包,即提供了Comparable接口(对应于writableComparable接口)和Comparator类(对应于RawComparator类)用于实现序列化的比较,在hadoop 的IO包中已经封装了JAVA的基本数据类型用于序列化和反序列化,一般自己写的类实现序列化和反序列化需要继承WritableComparable接口并且内置一个Comparator(继承于WritableComparator)的格式来实现自己的对象。