Hadoop的MR自定义排序其实很简单,其本质就是扩展了自定义Bean的Key,比如下面这个例子,我希望完成订单价格的排序
在正式进入代码编程之前,我先要给大家介绍两个接口,一个是WritableComparable,另外一个是Writable,这两个接口啊,就是给我们提供的,让我们自定义数据类型的,他们两个的区别就在于Writable,不能用来自定义key,因为我在MR的原理中说过了,MR在运行的时候会将数据进行排序,且是以k为核心,所以自定义key的时候必须需要其中有排序的方法
至于为什么不去使用JAVA提供的排序接口,这一点我在MR原理中已经说了,这已经不在多说了,想了解的可以翻翻我其他的博文
而现在啊,我要给大家说的是MR之所以能够用key实现自定义排序,这和它本身的设计有关系,MR是磁盘计算,在可操作度上我们可以理解为他每一次实现的操作只相当于spark的一个算子,所以自定义排序,的代码,大家会发现一个有意思的事情,就是我们通常写自定义排序代码之后,都是按照key去排序,而value上不留任何东西,因为无意义,我们操作自定义排序计算的时候,其实就是相当于舍弃掉了组内成员的操作意义,只为了让不同的组之间排序,因为MR的底层就是用排序去区分组,因此我们现在将它的意义原始化,不再考虑MR排序等价分组,而是直接使用它的排序意义让组合组之间排序
有人会有疑问,既然我们,舍弃组内成员的操作意义,那为什么reducer还有循环?有这个疑问的,我只能说你考虑问题不够全面,要知道我们现在只是在我们操作的意义上舍弃了组内的操作,但是其实MR还是存在分组意义,因此还是会发生数据在同一组的情况,所以我们才遍历了数据为空的集合
至于为什么我们动的空集还能操作不同的key,有这个疑问的人,那么你对MR的原理应该去多想一想,多想一想你就会发现,其实MR只是完成了分组,并没有把相同的key和为一个,而且我们作为程序员,应该知道写代码的五大设计要求,其中有一个要求,我们在写代码的时候只填代码,哪怕一个东西淘汰了,我们最多只是标记不再使用,不到万不得已下不删代码,我大胆猜测,MR也是这个样子,他有这样自定义排序需求的时候,他如果在对组内进行一次排序,那么这个计算量度与本身就体系非常庞大的MR来说就太累赘了,但是本身设计又有限制,所以他以一个key的形式向我们展示可操作的数据,但是其实它内部在运行时,Key和我们在便利的那个value,其实指向的是一对数据,所以key随着变化而变化
package com.wy;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class OrderBean implements WritableComparable<OrderBean>{
private int orderId;
private double price;
public OrderBean() {
}
public void setOrderBean(int orderId, double price) {
this.orderId = orderId;
this.price = price;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
/**
* 我写的是二级排序,大家可以根据自己的需要书写排序
*/
@Override
public int compareTo(OrderBean o) {
int price=Double.compare(o.price,this.price);
int id = Integer.compare(o.orderId, this.orderId);
if ( id == 0 ){
return price;
}else {
return id;
}
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeInt(this.orderId);
dataOutput.writeDouble(this.price);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.orderId=dataInput.readInt();
this.price=dataInput.readDouble();
}
@Override
public String toString() {
return orderId +
"\t" + price ;
}
}
大家仔细看这个类,其实就会发现,就像我说的那样,就是扩展了compareTo方法,但是我们要注意书写的排序在key上才会生效,因为MapReduce的底层在分组排序的时候是以key为核心的,同时我们书写的时候也要注意逻辑,因为排序的级别越高,那么就可能导致本来在一个组的数据,在洗牌之后变成了不同的组,而使用这个类的时候也不需要另外用job对象做设置照常写就好
public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException {
Configuration cfg = new Configuration();
//获取到任务
Job job = Job.getInstance(cfg);
job.setJarByClass(OrderDirver.class);
//对输入输出参数设置
job.setOutputKeyClass(OrderBean.class);
job.setOutputValueClass(NullWritable.class);
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(NullWritable.class);
//设置map reduce类
job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
//设置输入输出路径
FileInputFormat.setInputPaths( job , new Path("D:\\a\\input"));
FileOutputFormat.setOutputPath( job , new Path("D:\\a\\output"));
boolean b = job.waitForCompletion(true);
System.exit(b == true ? 0 : -1);
}