二次排序原理
写在前面
在MapReduce编程框架下,当我们要对数据进行排序时,如下所示数据,我们希望先根据Name进行排序,然后再Name相同的情况下,根据Date进行排序。这就是所谓的二次排序。
Name Date Site Count
harry w6d3 v10 1
harry w6d7 v7 1
harry w6d1 v1 2
jerry w6d3 v10 1
jack w6d3 v1 2
jerry w6d6 v4 1
jack w6d6 v9 2
以下数据二次排序的结果为:
Name Date Site Count
harry w6d1 v1 2
harry w6d3 v10 1
harry w6d7 v7 1
jack w6d3 v1 2
jack w6d6 v9 2
jerry w6d3 v10 1
jerry w6d6 v4 1
二次排序工作原理
简单实现
由于MapReduce处理的是键-值对,在Map阶段读入数据后,在输出时,根据输出定义的键值(key)进行排序。此时,我们需要做的就是定义一个自定义的Writable类型——TextPair,此类型包含两次排序的元素,即Name,Date。
定制TextPair的实现如下代码所示:
public static class TextPair implements WritableComparable<TextPair> {
private Text Name;
private Text Date;
//构造器
public TextPair() {
set(new Text(),new Text());
}
//set方法
public void set(Text left, Text right) {
this.Name = left;
this.Date = right;
}
//get方法
public Text getName() {
return this.Name;
}
public Text getDate() {
return this.Date;
}
//反序列化
public void readFields(DataInput in) throws IOException {
Name.readFields(in);
Date.readFields(in);
}
//序列化
public void write(DataOutput out) throws IOException {
Name.write(out);
Date.write(out);
}
//重写hashCode方法
public int hashCode() {
return this.Name.hashCode() * 157 + this.Date.hashCode();
}
//重写equals方法
public boolean equals(Object right) {
if ((right instanceof TextPair)) {
TextPair r = (TextPair) right;
return (r.Name.equals(this.Name) && r.Date.equals(this.Date));//注意此处用的是equals方法
}
return false;
}
//重写compareTo方法
@Override
public int compareTo(TextPair o) {
int cmp =Name.compareTo(o.getName());
if(cmp!=0){
return cmp;
}
return Date.compareTo(o.getDate());
}
}
此处自定义的TextPair的实现第一部分很直观:包括两个Text实例变量(Name和Date)和相关的构造函数,以及Setter、getter方法。然后再调用readFields()函数查看(填充)各个字段的值。TextPair类的write()方法依次对每个Text对象序列化到输出流中。类似的, 通过每个Text对象表示,readFields()对来自输入流的字节进行反序列化。
由于MapReduce中默认分区通常用hashCode()方法来选择reduce分区,所以,要确保有一个比较好的hash函数来保证每个reduce分区的大小相当。
TextPair是WritableComparable的一个实现,所以它提供了compareTo()方法,该方法可以强制数据排序。先按照第一个字符(Name)排序,如果第一个字符相同,则按照第二个字符(Date)排序。以上程序完全可以实现二次排序的功能。然而,此种方法并不是最优的方式,当TextPair被用作MapReduce中的键(key)时,需要将数据流反序列化为对象,然后再调用compareTo()方法进行比较,若能在序列化的状态下就直接比较两个TextPair对象,就不需要反序列化后再比较,这样效率就提高了。
优化比较速度
因为TextPair是两个Text对象连接而成,而Text对象的二进制表示是一个长度可变的整数,包含字符串的UTF-8表示的字节数以及UTF-8字节本身。诀窍在于读取该对象的起始长度,由此得知第一个Text对象的字节表示有多长;然后将该对象的长度传给Text对象的RawComparator方法,最后通过计算第一个字符串和第二个字符串恰当的偏移量,这样可以实现对象的比较。详细过程如下(注意,这段代码已嵌入TextPair):
public static class Comparator extends WritableComparator {
private static final Text.Comparator TEXT_COMPARATOR = new Text.Comparator();
public Comparator() {
super(TextPair.class);
}