目录
Hadoop版本: 3.1.3
通过分析MapReduce比较器获取的源码可知,对Key排序的比较器都是WritableComparator。下面就来一探WritableComparator的源码,看它是如何实现的。(comparator的获取可以参考:
Hadoop源码分析:Comparator的获取
。)
一、WritableComparator部分源码分析
WritableComparator是Comparator的一个实现类,该类定义了一个HashMap集合comparators,通过define()方法把Class c 类的比较器以<c.class,comparator>键值对的方式存入到comparators。换句话说,就是把C类的comparator存放在comparators中,这样在有需要的时候可以根据对应的Class c,获得与之对应的比较器。
public class WritableComparator implements RawComparator, Configurable {
private static final ConcurrentHashMap<Class, WritableComparator> comparators =
new ConcurrentHashMap();
public static void define(Class c, WritableComparator comparator) {
comparators.put(c, comparator);
}
同时,该类提供了获得比较器的get方法,如下源码。该方法内部调用了Hashmap的get()方法,通过key的值,获得与传入参数key对应的比较器。但comparators中只存储了hadoop所属类的比较器,自定义类的比较器无法获得。
public static WritableComparator get(Class<? extends WritableComparable> c, Configuration conf) {
WritableComparator comparator = (WritableComparator)comparators.get(c);
if (comparator == null) {
forceInit(c);
comparator = (WritableComparator)comparators.get(c);
if (comparator == null) {
comparator = new WritableComparator(c, conf, true);
}
}
ReflectionUtils.setConf(comparator, conf);
return comparator;
}
若是自定义类,get()方法无法获得comparator,只能创建新的比较器,那么新的比较器是如何实现可比较的呢?
在WritableComparator类中,封装了三个compare方法,新创建的comparator默认就是使用的该compare方法。从源码可知,3个compare()方法重载,第1个compare()和第3个compare()都是间接调用的第2个compare()方法。
通过第2个compare()可知,比较的Key的类型必须是WritableComparable类,且调用了key类的compareTo()方法。因此,就要求自定义的类必须实现WritableComparable接口,并实现compareTo()。
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
try {
this.buffer.reset(b1, s1, l1);
this.key1.readFields(this.buffer);
this.buffer.reset(b2, s2, l2);
this.key2.readFields(this.buffer);
this.buffer.reset((byte[])null, 0, 0);
} catch (IOException var8) {
throw new RuntimeException(var8);
}
return this.compare(this.key1, this.key2);
}
public int compare(WritableComparable a, WritableComparable b) {
return a.compareTo(b);
}
public int compare(Object a, Object b) {
return this.compare((WritableComparable)a, (WritableComparable)b);
}
综上分析可知,若是Mapreduce中的Key为自定义类时,从系统comparators集合中无法获取对应的比较器,若是在未提供比较器的情况下,程序内部会创建一个新的comparator,且默认最终调用的方法是自定义类的compareTo()方法。
二、Hadoop的Text类分析
Text类是hadoop的所需类,如上所说,Text类在进行类加载时,就会将对应的比较器存入comparators集合中。下边就从源码的角度分析具体的实现方式。
在Text类中,有个继承了WritableComparator的嵌套类Comparator,如下源码。此Comparator类中声明了一个Compare()方法,用来实现Text的可比较。另外,在Text类中,有一个初始化类的静态代码块,其中执行了define()方法。此方法的作用就是将Text类的comparator存入comparators集合中。Hadoop的其他类IntWritable、DoubleWritable等,实现方式与Text相同。
public class Text extends BinaryComparable implements
WritableComparable<BinaryComparable> {
static {
WritableComparator.define(Text.class, new Text.Comparator());
bytesFromUTF8 = new int[]{...};
offsetsFromUTF8 = new int[]{0, 12416, 925824, 63447168, -100130688, -2113396608};
}
public static class Comparator extends WritableComparator {
public Comparator() {
super(Text.class);
}
//compare方法
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
int n1 = WritableUtils.decodeVIntSize(b1[s1]);
int n2 = WritableUtils.decodeVIntSize(b2[s2]);
return compareBytes(b1, s1 + n1, l1 - n1, b2, s2 + n2, l2 - n2);
}
}
三、自定义比较器
通过对Text类比较器的源码分析,可知,自定义比较器需继承WritableComparator接口。从WritableComparable中的compare()方法可知,1、3compare()方法的最终调用都是第2个compare方法。
所以自定义的比较器,需要继承WritableComparator,并重写第2个compare()方法。如下:
public class KeyComparator extends WritableComparator {
@Override
public int compare(WritableComparable a, WritableComparable b) {
Key aKey = (Key)a;
Key bKey = (Key)b;
return ... ;//此处可以自定义排序规则
}
}
综合以上分析可知,comparator的优先级是最高的,只要有定义,则会采用comparator定义的比较规则。若没有自定义比较器,或者比较器没有重写compare()方法,则最终还是会采用Key类的compareTo()方法。