十一、 HADOOP-自定义排序及分组排序

1、 自定义排序

可通过实现writableComparable接口来实现自定义排序,并且使用此自定义类作为Mapper的输出key,例如有下面的订单
订单ID 商品ID 价格
0000001 Pdt_01 222.8
0000002 Pdt_06 722.4
0000001 Pdt_05 25.8
0000003 Pdt_01 222.8
0000003 Pdt_01 33.8
0000002 Pdt_03 522.8
0000002 Pdt_04 122.4
我们构造订单对象,并通过订单id排序

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Order implements WritableComparable<Order> {
    private String orderId;
    private String goodsId;
    private Double goodsPrice;
    /**
     * 按价格倒序
     * @param o
     * @return
     */
    public int compareTo(Order o) {
        //比较两个orderid
        return this.orderId.compareTo(o.getOrderId());
    }
    //序列化
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.write((orderId + "\001" + goodsId + "\001" + goodsPrice).getBytes());
    }
    //反序列化
    public void readFields(DataInput dataInput) throws IOException {
        String[] res = dataInput.readLine().split("\001");
        this.orderId = res[0];
        this.goodsId = res[1];
        this.goodsPrice = Double.parseDouble(res[2]);
    }

    @Override
    public String toString() {
        return this.orderId + "\t" + this.goodsId + "\t" + this.goodsPrice;
    }
}

2、 二次排序

二次排序,即在原有排序中,有相同的数据,我们通过第二个参数来进行排序,参与字段为2个,例如上面的例子,我们在订单ID相同的时候,再使用价格倒序排序,代码如下

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Order implements WritableComparable<Order> {
    private String orderId;
    private String goodsId;
    private Double goodsPrice;
    /**
     * 按价格倒序
     * @param o
     * @return
     */
    public int compareTo(Order o) {
        //比较两个orderid
        int order = this.orderId.compareTo(o.getOrderId());
        //如果orderid相等,则返回价格的比较结果的相反值(倒序排列),否则返回order的比较结果
        return order != 0 ? order : -1 * (this.goodsPrice).compareTo(o.goodsPrice);
    }
    //序列化
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.write((orderId + "\001" + goodsId + "\001" + goodsPrice).getBytes());
    }
    //反序列化
    public void readFields(DataInput dataInput) throws IOException {
        String[] res = dataInput.readLine().split("\001");
        this.orderId = res[0];
        this.goodsId = res[1];
        this.goodsPrice = Double.parseDouble(res[2]);
    }
    @Override
    public String toString() {
        return this.orderId + "\t" + this.goodsId + "\t" + this.goodsPrice;
    }
}

3、 分组排序

分组排序,即对数据进行分组,然后在组内排序,通过实现WritableComparator来创建一个自定义分组类,在job中,使用setGroupingComparatorClass方法设置分组类
例如上面例子,我们需要求出每个订单中价格最贵的商品

package top.gujm.gourpsort;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Order implements WritableComparable<Order> {
    private String orderId;
    private String goodsId;
    private Double goodsPrice;
    /**
     * 按orderid排序升序,按价格倒序
     * 底层判断分组,只会比较相邻两个数据的WritableComparator.compare(data1,data2)的值是否为0,如果为0,是同一分组,否则不同分组
     * 所以在这里要先通过订单id排序,确保相同订单号的订单是相邻的
     * @param o
     * @return
     */
    public int compareTo(Order o) {
        //比较两个orderid
        int order = this.orderId.compareTo(o.getOrderId());
        //如果orderid相等,则返回价格的比较结果的相反值(倒序排列),否则返回order的比较结果
        return order != 0 ? order : -1 * (this.goodsPrice).compareTo(o.goodsPrice);
    }
    //序列化
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.write((orderId + "\001" + goodsId + "\001" + goodsPrice).getBytes());
    }
    //反序列化
    public void readFields(DataInput dataInput) throws IOException {
        String[] res = dataInput.readLine().split("\001");
        this.orderId = res[0];
        this.goodsId = res[1];
        this.goodsPrice = Double.parseDouble(res[2]);
    }

    @Override
    public String toString() {
        return this.orderId + "\t" + this.goodsId + "\t" + this.goodsPrice;
    }
}
package top.gujm.gourpsort;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

public class Group extends WritableComparator {
    public Group(){
        super(Order.class, true);
    }
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        //通过此方法,来判断两个订单是否是相同订单,即是否为同一分组,如果返回值为0,则为同一分组
        //向上转型
        Order a1 = (Order)a;
        Order b1 = (Order)b;
        //比较oderid进行分组及排序
        return a1.getOrderId().compareTo(b1.getOrderId());
    }
}
package top.gujm.gourpsort;

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 OrderMapper extends Mapper<LongWritable, Text, Order, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //分割行数据
        String[] fields = value.toString().split("\t");
        //构造订单对象
        Order order = new Order(fields[0], fields[1], Double.parseDouble(fields[2]));
        //写出数据
        context.write(order, NullWritable.get());
    }
}
package top.gujm.gourpsort;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class OrderReduce extends Reducer<Order, NullWritable, Order, NullWritable> {
    @Override
    protected void reduce(Order key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        //写出数据
        context.write(key, NullWritable.get());
    }
}
package top.gujm.utils;

import org.apache.commons.beanutils.converters.DateTimeConverter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;


import java.io.IOException;
public class JobUtils {
    private static Class partitionerClass = null;
    private static int reduceTasks = 1;
    private static long maxSize = 0;
    private static long minSize = 0;
    private static boolean useCombineTextInputFormat = false;
    private static Class groupingComparator = null;
    /**
     * 创建一个job任务
     * @param jarClass 对应的Jar的类型
     * @param mapperClass Mapper的类型
     * @param mapperOutputKeyClass Mapper的输出key的类型
     * @param mapperOutputValueClass Mapper的输出value类型
     * @param reducerClass Reducer类型
     * @param reducerOutputKeyClass Reducer输出key类型
     * @param reducerOutputValueClass Reducer输出value类型
     * @param inputPath 输入文件路径
     * @param outPath 输出路径
     * @return Job任务
     * @throws IOException 异常
     */
    public static Job createJob(
            Class jarClass,
            Class mapperClass, Class mapperOutputKeyClass, Class mapperOutputValueClass,
            Class reducerClass, Class reducerOutputKeyClass, Class reducerOutputValueClass,
            String inputPath, String outPath
    ) throws IOException {
        //通过configuration获取到job实例
        Job job = Job.getInstance(new Configuration());
        //设置job的主类
        job.setJarByClass(jarClass);
        //设置Mapper的类
        job.setMapperClass(mapperClass);
        //设置Mapper的输出的key类型
        job.setMapOutputKeyClass(mapperOutputKeyClass);
        //设置Mapper的输出的value类型
        job.setMapOutputValueClass(mapperOutputValueClass);
        if(reducerClass != null) {
            //设置Reducer的类
            job.setReducerClass(reducerClass);
            //设置Reducer的输出的key类型(也是整个job的输出类型)
            job.setOutputKeyClass(reducerOutputKeyClass);
            //设置Reducer的输出的value类型(也是整个job的输出类型)
            job.setOutputValueClass(reducerOutputValueClass);
        }
        //job设置分组排序
        if(groupingComparator != null) {
            job.setGroupingComparatorClass(groupingComparator);
        }
        //设置自定义partiniter
        if(partitionerClass != null){
            //设置partiniter类
            job.setPartitionerClass(partitionerClass);
            //根据partitioner逻辑,设置reduce任务数
            job.setNumReduceTasks(reduceTasks);
        }
        //使用CombineTextInputFormat
        if(useCombineTextInputFormat){
            job.setInputFormatClass(CombineTextInputFormat.class);
            if(maxSize > 0) {
                CombineTextInputFormat.setMaxInputSplitSize(job, maxSize);
            }
            if(minSize > 0) {
                CombineTextInputFormat.setMinInputSplitSize(job, minSize);
            }
        }
        //设置输入文件
        FileInputFormat.setInputPaths(job, new Path(inputPath));
        //设置输出文件
        FileOutputFormat.setOutputPath(job, new Path(outPath));
        return job;
    }
    /**
     * 设置分组排序的类
     * @param groupingComparator
     */
    public static void setGroupingComparator(Class groupingComparator){
        JobUtils.groupingComparator = groupingComparator;
    }
    /**
     * 设置Partitioner类(分区)
     * @param partitionerClass partitioner类
     * @param reduceTasks reduce任务数
     */
    public static void setPartitioner(Class partitionerClass, int reduceTasks){
        JobUtils.partitionerClass = partitionerClass;
        JobUtils.reduceTasks = reduceTasks;
    }
    /**
     * 使用CombineTextInputFormat
     * @param maxSize 最大
     * @param minSize 最小
     */
    public static void useCombineTextInputFormat(long maxSize, long minSize){
        JobUtils.useCombineTextInputFormat = true;
        JobUtils.maxSize = maxSize;
        JobUtils.minSize = minSize;
    }
}
package top.gujm.gourpsort;

import org.apache.hadoop.io.NullWritable;
import top.gujm.utils.JobUtils;

import java.io.IOException;

public class OrderDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        JobUtils.setGroupingComparator(Group.class);
        JobUtils.createJob(
                OrderDriver.class,
                OrderMapper.class,
                Order.class,
                NullWritable.class,
                OrderReduce.class,
                Order.class,
                NullWritable.class,
                args[0],
                args[1]
        ).waitForCompletion(true);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容