一、map自定义排序
哪个字段需要排序,将其设为map输出的key,利用map的排序完成。
- 如果字段为基本类型且正序排序,则直接设为key,利用map默认排序即可。
- 如果字段为对象或需要倒序排序,则需利用对象类实现comparable(WritableComparable)接口,重写接口的comparable方法。
二、map自定义分组
需新建分组类,继承WritableComparator类,重写compare方法,返回0则归为一组。
main方法中需指定分组类。
三、举例
现有订单及对应单价的原始数据,每笔订单有不同产品对应不同单价,要求MapReduce最终输出每笔订单的最大单价。
订单号 | 产品号 | 价格 |
---|---|---|
0001 | prod1 | 200 |
0001 | prod2 | 300 |
0001 | prod3 | 400 |
0002 | prod2 | 350 |
0002 | prod3 | 450 |
0003 | prod4 | 800 |
0003 | prod5 | 1500 |
0003 | prod6 | 1200 |
0004 | prod6 | 1200 |
代码如下:
1.对象类Order
用以封装订单号及价格。
- 建立无参、满参构造方法,set/get方法,重写toString方法
- 继承writableCompareble接口,重写compareTo/write/readFields方法
- compareTo方法:返回1则指定的数大于参数;-1则小于参数;0则等于参数。
- write方法序列化方法
- readFields反序列化方法
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class Order implements WritableComparable<Order> {
private int orderId;
private double price;
//set/get/构造方法省略
//重写toString方法
@Override
public String toString() {
return orderId+"\t"+price;
}
//先对OrderID排序,正序倒序无所谓,当OrderID相同的时候,对价格排序,倒序,即当this.getPrice() > o.getPrice() 时,返回 -1则认为是倒序,1则是正序。
public int compareTo(Order o) {
return this.getOrderId() > o.getOrderId() ? 1 : (this.getOrderId() < o.getOrderId() ? -1 : (this.getPrice() > o.getPrice() ? -1 : 1));
}
//hadoop参数传递的序列化方法
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeInt(orderId);
dataOutput.writeDouble(price);
}
//反序列化方法
public void readFields(DataInput dataInput) throws IOException {
this.orderId=dataInput.readInt();
this.price=dataInput.readDouble();
}
}
2.分组类 OrderGrouping
新建类继承WritableComparator类,重写compare方法,返回值为0时,归为一组。
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class OrderGrouping extends WritableComparator {
//固定套路,允许通过反射创建实例
protected OrderGrouping(){
super(Order.class,true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
//类型强转为Order
Order abean=(Order) a;
Order bbean=(Order) b;
//按OrderID分组,当两个OrderID相同时,返回0,则被认为是同一组;
return abean.getOrderId()-bbean.getOrderId();
}
}
3.Mapper类LargestOrderMapper
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class LargestOrderMapper extends Mapper<LongWritable,Text,Order,NullWritable> {
IntWritable v = new IntWritable();
Order k = new Order();
//获取数据,封装到Order对象中,传递给Reduce
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
k.setOrderId(Integer.parseInt(split[0]));
k.setPrice(Double.parseDouble(split[2]));
context.write(k,NullWritable.get());
}
}
4.Reducer类LargestOrderReducer
reduce输出的key,随着每组iterable中的迭代而迭代,并非固定的key。
若想输出的key是每组中的第一个key,则直接输出无需迭代;
若想输出每组最后一个key,则迭代后取值即可。
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class LargestOrderReducer extends Reducer<Order,NullWritable,Order,NullWritable> {
@Override
protected void reduce(Order key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//因为在同一分组内price倒序,因此直接取每组第一个order类,即为该组的price最大值
context.write(key,NullWritable.get());
}
}
5.Main方法所在类LargestOrderDriver
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class LargestOrderDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
//设定本地执行
conf.set("mapreduce.framework.name", "local");
Job job = Job.getInstance(conf);
Job.getInstance();
//指定我这个 job 所在的 jar包位置
job.setJarByClass(LargestOrderDriver.class);
//指定map分组的类
job.setGroupingComparatorClass(OrderGrouping.class);
//指定我们使用的Mapper是那个类 reducer是哪个类
job.setMapperClass(LargestOrderMapper.class);
job.setReducerClass(LargestOrderReducer.class);
// 设置我们的业务逻辑 Mapper 类的输出 key 和 value 的数据类型
job.setMapOutputKeyClass(Order.class);
job.setMapOutputValueClass(NullWritable.class);
// 设置我们的业务逻辑 Reducer 类的输出 key 和 value 的数据类型
job.setOutputKeyClass(Order.class);
job.setOutputValueClass(NullWritable.class);
// 指定数据位置
FileInputFormat.setInputPaths(job, new Path("F:\\IT\\input\\新建文本文档.txt"));
// 指定处理完成之后的结果所保存的位置
FileOutputFormat.setOutputPath(job, new Path("F:\\IT\\output1"));
// 向 yarn 集群提交这个 job
boolean res = job.waitForCompletion(true);
System.exit(res ? 0 : 1);
}
}
四、拓展 需求更进一步
相对于取price最大值,如何取排前N项的price(比如2?)
总体思路:
排序及分组无需改变,只需在reduce时,相对于直接输出最大值,改为迭代,if判断迭代到的是第几个即可。符合条件的进行输出。
因此之前传递的value不能取NullWritable,而取一个有意义的值,比如price,以便可以迭代。
修改mapper及reducer如下:
mapper
传递值改为DoubleWritable
public class LargestOrderTopNMapper extends Mapper<LongWritable,Text,Order,DoubleWritable> {
Order k = new Order();
DoubleWritable v = new DoubleWritable();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
k.setOrderId(Integer.parseInt(split[0]));
k.setPrice(Double.parseDouble(split[2]));
v.set(Double.parseDouble(split[2]));
context.write(k,v);
}
}
Reducer
接收值改为DoubleWritable
增加输出判断条件
public class LargestOrderTopNReducer extends Reducer<Order, DoubleWritable, Order, NullWritable> {
@Override
protected void reduce(Order key, Iterable<DoubleWritable> values, Context context) throws IOException, InterruptedException {
int count = 0;
for (DoubleWritable value : values) {
//排名前二输出
if (count <= 1) {
context.write(key, NullWritable);
}
count++;
}
}
}
相应修改Driver类中传递的参数类型。