一、写在之前的

1.1 回顾Map阶段四大步骤

  首先,我们回顾一下在MapReduce中,排序和分组在哪里被执行:

  从上图中可以清楚地看出,在Step1.4也就是第四步中,需要对不同分区中的数据进行排序和分组,默认情况下,是按照key进行排序和分组。

1.2 实验场景数据文件

  在一些特定的数据文件中,不一定都是类似于WordCount单次统计这种规范的数据,比如下面这类数据,它虽然只有两列,但是却有一定的实践意义。

3    3
3 2
3 1
2 2
2 1
1 1

  (1)如果按照第一列升序排列,当第一列相同时,第二列升序排列,结果如下所示

1    1
2 1
2 2
3 1
3 2
3 3

  (2)如果当第一列相同时,求出第二列的最小值,结果如下所示

3    1
2 1
1 1

  接着,我们会针对这个数据文件,进行排序和分组的实践尝试,以求达到结果所示的效果。

二、初步探索排序

2.1 默认的排序

  在Hadoop默认的排序算法中,只会针对key值进行排序,我们最初的代码如下(这里只展示了map和reduce函数):

public class MySortJob extends Configured implements Tool {

    public static class MyMapper extends
Mapper<LongWritable, Text, LongWritable, LongWritable> {
protected void map(
LongWritable key,
Text value,
Mapper<LongWritable, Text, LongWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
String[] spilted = value.toString().split("\t");
long firstNum = Long.parseLong(spilted[0]);
long secondNum = Long.parseLong(spilted[1]); context.write(new LongWritable(firstNum), new LongWritable(
secondNum));
};
} public static class MyReducer extends
Reducer<Text, LongWritable, Text, LongWritable> {
protected void reduce(
LongWritable key,
java.lang.Iterable<LongWritable> values,
Reducer<LongWritable, LongWritable, LongWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
for (LongWritable value : values) {
context.write(key, value);
}
};
} }

  这里我们将第一列作为了key,第二列作为了value。

  可以查看一下运行后的结果,如下所示:

1    1
2 2
2 1
3 3
3 2
3 1

  从运行结果来看,并没有达到我们最初的目的,于是,我们需要抛弃默认的排序规则,因此我们要自定义排序。

2.2 自定义排序

  (1)封装一个自定义类型作为key的新类型:将第一列与第二列都作为key

    private static class MyNewKey implements WritableComparable<MyNewKey> {
long firstNum;
long secondNum; public MyNewKey() {
} public MyNewKey(long first, long second) {
firstNum = first;
secondNum = second;
} @Override
public void write(DataOutput out) throws IOException {
out.writeLong(firstNum);
out.writeLong(secondNum);
} @Override
public void readFields(DataInput in) throws IOException {
firstNum = in.readLong();
secondNum = in.readLong();
} /*
* 当key进行排序时会调用以下这个compreTo方法
*/
@Override
public int compareTo(MyNewKey anotherKey) {
long min = firstNum - anotherKey.firstNum;
if (min != 0) {
// 说明第一列不相等,则返回两数之间小的数
return (int) min;
} else {
return (int) (secondNum - anotherKey.secondNum);
}
}
}

PS:这里为什么需要封装一个新类型呢?因为原来只有key参与排序,现在将第一个数和第二个数都参与排序,作为一个新的key。

  (2)改写最初的MapReduce方法函数代码:(只展示了map和reduce函数,还需要修改map和reduce输出的类型设置)

        public static class MyMapper extends
Mapper<LongWritable, Text, MyNewKey, LongWritable> {
protected void map(
LongWritable key,
Text value,
Mapper<LongWritable, Text, MyNewKey, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
String[] spilted = value.toString().split("\t");
long firstNum = Long.parseLong(spilted[0]);
long secondNum = Long.parseLong(spilted[1]);
// 使用新的类型作为key参与排序
MyNewKey newKey = new MyNewKey(firstNum, secondNum); context.write(newKey, new LongWritable(secondNum));
};
} public static class MyReducer extends
Reducer<MyNewKey, LongWritable, LongWritable, LongWritable> {
protected void reduce(
MyNewKey key,
java.lang.Iterable<LongWritable> values,
Reducer<MyNewKey, LongWritable, LongWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
context.write(new LongWritable(key.firstNum), new LongWritable(
key.secondNum));
};
}

  从上面的代码中我们可以发现,新类型MyNewKey实现了一个叫做WritableComparable的接口,该接口中有一个compareTo()方法,当对key进行比较时会调用该方法,而我们将其改为了我们自己定义的比较规则,从而实现我们想要的效果。

  其实,这个WritableComparable还实现了两个接口,我们看看其定义:

public interface WritableComparable<T> extends Writable, Comparable<T> {
}

  Writable接口是为了实现序列化,而Comparable则是为了实现比较。

  (3)现在看看运行结果:

1    1
2 1
2 2
3 1
3 2
3 3

  运行结果与预期的已经一致,自定义排序生效!

三、初步探索分组

3.1 默认的分组

  在Hadoop中的默认分组规则中,也是基于Key进行的,会将相同key的value放到一个集合中去。这里以上面的例子继续看看分组,因为我们自定义了一个新的key,它是以两列数据作为key的,因此这6行数据中每个key都不相同,也就是说会产生6组,它们是:1 1,2 1,2 2,3 1,3 2,3 3。而实际上只可以分为3组,分别是1,2,3。

  现在首先改写一下reduce函数代码,目的是求出第一列相同时第二列的最小值,看看它会有怎么样的分组:

    public static class MyReducer extends
Reducer<MyNewKey, LongWritable, LongWritable, LongWritable> {
protected void reduce(
MyNewKey key,
java.lang.Iterable<LongWritable> values,
Reducer<MyNewKey, LongWritable, LongWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
long min = Long.MAX_VALUE;
for (LongWritable number : values) {
long temp = number.get();
if (temp < min) {
min = temp;
}
} context.write(new LongWritable(key.firstNum), new LongWritable(min));
};
}

  其运行结果为:

1    1
2 1
2 2
3 1
3 2
3 3

  但是我们预期的结果为:

#当第一列相同时,求出第二列的最小值
3 3
3 2
3 1
2 2
2 1
1 1
-------------------
#预期结果应该是
3 1
2 1
1 1

3.2 自定义分组

  为了针对新的key类型作分组,我们也需要自定义一下分组规则:

  (1)编写一个新的分组比较类型用于我们的分组:

    private static class MyGroupingComparator implements
RawComparator<MyNewKey> { /*
* 基本分组规则:按第一列firstNum进行分组
*/
@Override
public int compare(MyNewKey key1, MyNewKey key2) {
return (int) (key1.firstNum - key2.firstNum);
} /*
* @param b1 表示第一个参与比较的字节数组
*
* @param s1 表示第一个参与比较的字节数组的起始位置
*
* @param l1 表示第一个参与比较的字节数组的偏移量
*
* @param b2 表示第二个参与比较的字节数组
*
* @param s2 表示第二个参与比较的字节数组的起始位置
*
* @param l2 表示第二个参与比较的字节数组的偏移量
*/
@Override
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
return WritableComparator.compareBytes(b1, s1, 8, b2, s2, 8);
} }

  从代码中我们可以知道,我们自定义了一个分组比较器MyGroupingComparator,该类实现了RawComparator接口,而RawComparator接口又实现了Comparator接口,下面看看这两个接口的定义:

  首先是RawComparator接口的定义:

public interface RawComparator<T> extends Comparator<T> {
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2);
}

  其次是Comparator接口的定义:

public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}

  在MyGroupingComparator中分别对这两个接口中的定义进行了实现,RawComparator中的compare()方法是基于字节的比较,Comparator中的compare()方法是基于对象的比较。

  在基于字节的比较方法中,有六个参数,一下子眼花了:

Params:

* @param arg0 表示第一个参与比较的字节数组
* @param arg1 表示第一个参与比较的字节数组的起始位置
* @param arg2 表示第一个参与比较的字节数组的偏移量

* @param arg3 表示第二个参与比较的字节数组
* @param arg4 表示第二个参与比较的字节数组的起始位置
* @param arg5 表示第二个参与比较的字节数组的偏移量

  由于在MyNewKey中有两个long类型,每个long类型又占8个字节。这里因为比较的是第一列数字,所以读取的偏移量为8字节。

  (2)添加对分组规则的设置:

  // 设置自定义分组规则
job.setGroupingComparatorClass(MyGroupingComparator.class);

  (3)现在看看运行结果:

参考资料

(1)吴超,《深入浅出Hadoop》:http://www.superwu.cn/

(2)Suddenly,《Hadoop日记Day18-MapReduce排序和分组》:http://www.cnblogs.com/sunddenly/p/4009751.html

作者:周旭龙

出处:http://edisonchou.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

Hadoop学习笔记—11.MapReduce中的排序和分组的更多相关文章

  1. Hadoop学习笔记—12.MapReduce中的常见算法

    一.MapReduce中有哪些常见算法 (1)经典之王:单词计数 这个是MapReduce的经典案例,经典的不能再经典了! (2)数据去重 "数据去重"主要是为了掌握和利用并行化思 ...

  2. Hadoop学习笔记: MapReduce二次排序

    本文给出一个实现MapReduce二次排序的例子 package SortTest; import java.io.DataInput; import java.io.DataOutput; impo ...

  3. Hadoop学习笔记:MapReduce框架详解

    开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能 ...

  4. hadoop 学习笔记:mapreduce框架详解

    开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能 ...

  5. Hadoop学习笔记: MapReduce Java编程简介

    概述 本文主要基于Hadoop 1.0.0后推出的新Java API为例介绍MapReduce的Java编程模型.新旧API主要区别在于新API(org.apache.hadoop.mapreduce ...

  6. 【Big Data - Hadoop - MapReduce】hadoop 学习笔记:MapReduce框架详解

    开始聊MapReduce,MapReduce是Hadoop的计算框架,我学Hadoop是从Hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能 ...

  7. hadoop 学习笔记:mapreduce框架详解(转)

    原文:http://www.cnblogs.com/sharpxiajun/p/3151395.html(有删减) Mapreduce运行机制 下面我贴出几张图,这些图都是我在百度图片里找到的比较好的 ...

  8. SpringBoot学习笔记(11)-----SpringBoot中使用rabbitmq,activemq消息队列和rest服务的调用

    1. activemq 首先引入依赖 pom.xml文件 <dependency> <groupId>org.springframework.boot</groupId& ...

  9. 三、Hadoop学习笔记————从MapReduce到Yarn

    Yarn减轻了JobTracker的负担,对其进行了解耦

随机推荐

  1. asp.net core视图组件(ViewComponent)简单使用

    一.组成: 一个视图组件包括两个部分,派生自ViewComponent的类及其返回结果.类似控制器. 定义一个视图组件,如控制器一样,必须是公开,非嵌套,非抽象的类.一般,视图组件名称为类名去掉&qu ...

  2. Ubuntu菜鸟入门(七)—— 微信安装

    一.安装git sudo apt-get install git 设置自己的用户名和密码 sudo git config --global user.name "ABC" sudo ...

  3. selenium使用笔记(一)

    有时候在交流群里经常会看到这样的问题,selenium能进行性能测试吗?selenium1和selenium2有什么区别等等问题,在这里谈一下自己学习和工作以后对selenium的认识.我所记录的东西 ...

  4. Daily Scrum Meeting ——FourthDay(Beta)12.12

    一.Daily Scrum Meeting照片 讨论界面优化详情 二.Burndown Chart 项目有条不紊地进行中... 1.新增6个界面修改计划 2.修复两个BUG 三.项目进展(check- ...

  5. python 入门(一)矩阵处理

    numpy 使用 1.使用 array 定义矩阵 dataSet = array([[1.0,1.1],[1.0,1.0],[0.0,0.0],[0,0.1]]) 2.使用 shape 返回矩阵的行数 ...

  6. SOAPUI使用教程-从现有的服务创建REST模拟服务

    从现有的服务创建REST模拟服务 在您的项目创建一个模拟服务从现有的服务是非常简单的. 右键单击 服务 . 选择 生成模拟服务 . 打开一个对话框窗口. 输入一个描述性的名称. 点击 好吧 . 创建模 ...

  7. 数据库SQL优化大总结之 百万级数据库优化方案

    1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  8. Java数据结构——栈的应用(以数制转换为例子)

    看一万遍,不如自己动手实践,请不要直接copy代码,先自己去理解例子实现的原理,然后试着自己动手去实践. 用Java去实现栈操作,会用到stack这个类,这类中有几个方法需要知道他们的用法  bool ...

  9. C#中的null与void

    一.null: 1.明义,null是什么意思? null是指一个变量没有指向具体对象的有效引用. 这句话什么意思呢?意思就是 1).能够使用null修饰的是变量: 2).主要指的是引用. 那么这就引出 ...

  10. Spring中Aop的扩展及剖析

    AOP简介: 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范 ...