package day12_3;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.lib.HashPartitioner;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.junit.Test;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
//求每个订单中的最大单价的那行数据
// 订单编号 商品编号 价格
// Order_0000001,Pdt_01,222.8
// Order_0000001,Pdt_05,25.8
// Order_0000002,Pdt_05,325.8
// Order_0000002,Pdt_03,522.8
// Order_0000002,Pdt_04,122.4
// Order_0000003,Pdt_01,222.8
//思路:将订单编号,商品编号,价格封装成类
//实现该类的WritableComparable接口,使对象可以在MR中使用--重写序列化方法write,反序列化方法read()
//按订单编号(升序)和价格(降序)排序
//为什么要分区和分组?---要产生不同的文件。
// 要是按照默认hashcode分组的话,每个对象的hashcode都不一样,没办法让订单编号相同的
//被分到一个组---即被分到一个文件里
//分组---因为在reduce()方法时,要值相同的一组键掉用一次reduce()方法,因为键是多个不同的对象,会被认为是一个对象是单独的一组
//所以要写成符合我们条件的样子:在订单编号相同的情况下被认为是一组
//总结:
//所以要想生成三个文件,必须分区--这里让ID号相同的在一个区,用getPartition()方法实现
//必须实现分组,不然没办法在调用reduce()时,订单号相同的OrderBean对象被放进同一个迭代器
//为什么要把订单编号相同对象放进一个组??放进一个组是想用迭代器,拿到相同id的一组OrderBean对象,
//从而得到topN(topN是指倒序排序,取最上面的N个数据),
//要想对象序列化,且根据某属性有序,必须实现WritableComparable接口--这里根据D订单编号和价格排序
//键和值都存OrderBean是因为只有值能进行迭代,键已经是我们想要的结果,但是键没办法通过迭代器取出,
//所以把相同的键在值放一份,方便取出
//分区排序分组的顺序?
//写进缓冲区的80%数据,先反序列化,进行分区,再排序(从而达到分区内有序的效果),接着溢出(写到磁盘成为二进制的字符串),
//之后这些二进制字符串两两Merge(这里用归并排序,归并排序只让部分数据写进内存,也就是让部分二进制的数据反序列化为对象),
// 每个MapTask输出一个二进制文件,当一个这种文件被合并完,就开始启动Reduce端,然后在ReduceTask里会将所有的MapTask传来的
//二进制串最终合并成一个大文件,然后会把这个大文件按照它的索引,把不同区的数据放到它所对应的分区,之后调用reduce方法,
//对同组的数据进行迭代,这时候才涉及到分组。
public class Price {
//序列化且有序的类
public static class OrderBean implements WritableComparable<OrderBean>
{
private String orderId;
private String itemId;
private double price;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public OrderBean(){}
public void set(String orderId,String itemId,double price)
{
this.orderId=orderId;
this.itemId=itemId;
this.price=price;
}
//只要涉及实现Writable接口,将对象序列化和反序列话
//就必须写该类的无参构造方法,以为反序列化的时候先调用无参构造方法
//没有就会报错
@Override
public String toString() {
return orderId+"\t"+itemId+"\t"+price;
}
@Override
public int compareTo(OrderBean o) {
int a=this.orderId.compareTo(o.orderId);
int tmp=this.price-o.price>0? -1 :1;//价格降序
return a==0?tmp:a;
}
@Override//序列化方法
public void write(DataOutput out) throws IOException {
out.writeUTF(orderId);
out.writeUTF(itemId);
out.writeDouble(price);
}
@Override//反序列化方法
public void readFields(DataInput in) throws IOException {
orderId=in.readUTF();
itemId=in.readUTF();
price=in.readDouble();
}
}
//自定义Partirioner(分区器)
//默认的分区器为HashPartitioner,可以点进去看源码
public static class OrderPartitioner extends Partitioner<OrderBean,OrderBean>
{
@Override
//将orderId相同的OrederBean对象分在一个区,发往相同的reduceTask,生成一个文件
//默认的为HashPartitioner
public int getPartition(OrderBean key, OrderBean value, int numPartitions)
{
//&Integer.MAX_VALUE是因为源码是这么写的
return (key.getOrderId().hashCode()&Integer.MAX_VALUE)% numPartitions;
}
}
//自定义分组
//WritableComparator和WritableComparable的区别
//从单词意思上看Comparable是可比较的,Comparator是比较器
//WritableComparator只是单纯的一个比较器而已,它没有write()和read()方法
//WritableComparable是指可以让类的实例化对象可以进行比较
//为什么要用比较器?为什么不用默认的比较器(调用compareTo方法)
//因为这个题中我们写的compareTo方法只有当订单编号和价格一样才会返回0,
//而分组的话,是把订单编号相同认为是分成一个组,这就要求在订单编号相同的时候返回0
//所以要重写一个
public static class OrderGroupingComparator extends WritableComparator
{
public OrderGroupingComparator() {
//true表示创建一个构造器的实例
super(OrderBean.class,true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
OrderBean abean=(OrderBean)a;
OrderBean bbean=(OrderBean)b;
//方法的类型为WritableComparable,这个方法的实参是OrderBean
//OrderBean implements WritableComparable
//所以实参赋值给形参相当于子类的对象给父类的引用赋值(多态)
//所以这个父类对象可以转回子类(这样转的条件是多态,这里已经满足了)
String aBeanOrderId=abean.getOrderId();
String bBeanOrderId=bbean.getOrderId();
return aBeanOrderId.compareTo(bBeanOrderId);
}
}
//为什么键和值都存OrderBean?
//因为要输出TopN,这是通过迭代器输出的,而迭代器是针对值的
public static class MyMapper extends Mapper<LongWritable, Text,OrderBean,OrderBean>
{
//定义数据成员
private static OrderBean k=new OrderBean();
//在调用map()方法之前被自动调用,且只调用一次,相当于初始化
@Override
protected void setup(Context context) throws IOException, InterruptedException {
super.setup(context);
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line=value.toString();
String[] fields=line.split(",");
k.set(fields[0],fields[1],Double.parseDouble(fields[2]));
context.write(k,k);
}
//在mao()调用结束后被自动调用,且只调用一次,相当于垃圾回收
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
super.cleanup(context);
}
}
public static class MyReducer extends Reducer<OrderBean,OrderBean,OrderBean, NullWritable>
{
private static int topn;
//初始化
@Override
protected void setup(Context context) throws IOException, InterruptedException {
topn=Integer.parseInt(context.getConfiguration().get("topn"));
}
@Override
protected void reduce(OrderBean key, Iterable<OrderBean> values, Context context) throws IOException, InterruptedException {
int count=0;
for(OrderBean bean:values)
{
context.write(bean,NullWritable.get());
count++;
if(count==topn)
return;
}
}
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
super.cleanup(context);
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf=new Configuration();
conf.set("topn",args[0]);//动态获得topn的值
System.setProperty("HADOOP_USER_NAME", "root");
conf.set("fs.defaultFS","file:///");//在本地运行
conf.set("mapreduce.framework.name","local");
Job job=Job.getInstance(conf,"price");
job.setPartitionerClass(OrderPartitioner.class);//指定分区
job.setGroupingComparatorClass(OrderGroupingComparator.class);//指定分组
job.setNumReduceTasks(3);//设置reduceTask的个数
job.setJarByClass(Price.class);//设置jar包的位置,main方法在哪jar包就在哪
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
//设置Map端输出的key,value类型
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(OrderBean.class);
//设置Reduce端输出(也就是最终输出)的key,value的类型
job.setOutputKeyClass(OrderBean.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job,new Path("D:\\mrText\\price\\input"));
FileOutputFormat.setOutputPath(job,new Path("D:\\mrText\\price\\output"));
boolean b=job.waitForCompletion(true);
System.exit(b?0:1);
}
// @Test
// public void Text(){
// System.out.println(NullWritable.get()+" "+NullWritable.get());
// System.out.println("haha");
// }
}
输出: