引入和说明
熟悉mapreduce执行流程的都知道,mapreduce流程中,一共有两类排序,第一种是对于key的排序,默认是是根据key的递增排序。第二种是对于reduce的组排序,将两条记录的key带入到 分组函数中,如果返回0,则说明两个记录是一组的,所以就把他们的value合到一个迭代器中,也就是reduce函数的第二个参数。
最下面那个代码(小标题是总体代码)包括了 map ,reduce,两个key排序类,三个分组排序类,一个分区类,和一个主函数。也就是整个文章的所有代码。下面小片代码都是从里面提取出来的,建议先看一下最后那个代码,对整个代码有一个完整性认识。
最后那个代码段是整个程序用的实验数据,其实就是A-Z 和1-9顺序的穿插在一起。
分区
分区的代码如下
static public class myPartition extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key, IntWritable value, int numPartitions) {
char b = key.toString().charAt(0);
if (b >= 'A' && b <= 'Z') {
return 0;
}
if (b >= '0' && b <= '9')
return 1;
return 2;
}
}
这个分区函数很简单,就是将记录按照字母,数字和其他三种类型分别分到不同的是分区,即每次结果文件中会包括四个文件,分别是_SUCCESS、part-r-00000、part-r-00001、part-r-00002。part-r-00000储存key是字母的记录,part-r-00001存储key是数字的记录,part-r-00002储存key是其他的记录,因为数据都是我们自己写的,不会出现其他这种情况,所以以下文章都值看part-r-00000和part-r-00001两个文件。具体的分区效果在下面展示。
第0种组合—默认排序
运行jar时,输入第三个参数为0,即两个排序算法都不设置,用默认的即可,则结果文件如下
# part-r-00000文件
A 1
B 1
C 1
D 1
E 1
F 1
G 1
H 1
I 1
J 1
K 1
L 1
M 1
N 1
O 1
P 1
Q 1
R 1
S 1
T 1
U 1
V 1
W 1
X 1
Y 1
Z 1
# part-r-00001文件
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
从上面看出,分区的效果,即把字母和数字分开到两个结果文件中。
而且,从结果文件可以看出,默认的排序算法就是按字典顺序升序排列。而分组排序显示不出来,但是,源代码显示,分组排序跟key排序是一样的。
第一种组合
设置代码如下
if (Integer.valueOf(args[2]) == 1) {
System.out.println("==============1=================");
job.setSortComparatorClass(MySort.class);
}
MySort类的作用按照 key的第一个字符与3的余数升序排列,具体代码可以看倒数第二个代码段。
实验结果如下
# part-r-00000
N 9
L 8
D 9
# part-r-00001
3 3
4 3
8 3
是不是很奇怪是为什么是这种结果,其实原因是如果只设置了key排序算法而没有设置分组排序,那么分组排序也使用key排序的算法。如果我们只想设置key排序算法,那么我们还要设置把分组排序设置成默认排序。但是我不知道hadoop有没有默认排序的实现,所以我自己完成了一个默认排序,即按照字典排序升序排列(MyGroupSort2类)。这种实现是第五种组合,可以先去第五种组合那看,本文章本来就是穿插着写的,所以也应该穿插着看。
第二种组合
if (Integer.valueOf(args[2]) == 2) {
System.out.println("======================2=============");
job.setSortComparatorClass(MySort.class);
job.setGroupingComparatorClass(MyGroupSort.class);
}
这个组合设置了分组函数,设置的分组函数作用是 key的第一个字符与3的余数相同的key分为一组,其实,这个函数与第1种组合的key排序实现一样,我只不过是为了验证,当设置了key排序而没有设置分组排序是,分组排序使用key排序的函数。也就是说,如果这种组合的结果与第1种的结果一模一样,这么这个结论就可以完全证实了。
下面是这种组合的结果
# part-r-00000
N 9
L 8
D 9
# part-r-00001
3 3
4 3
8 3
可以看出,第2种组合和第1种组合的结果一模一样,当设置了key排序而没有设置分组排序是,分组排序使用key排序的函数。这个结论验证。
下面我们还要验证另一个结论,就是如果只设置分组排序,而不设置key排序,那么key排序会不会使用分组排序的函数。这个验证可以看第三种组合
第三种组合
if (Integer.valueOf(args[2]) == 3) {
System.out.println("======================3===========");
job.setGroupingComparatorClass(MyGroupSort.class);
}
运行结果如下
# part-r-00000
A 1
B 1
C 1
D 1
E 1
F 1
G 1
H 1
I 1
J 1
K 1
L 1
M 1
N 1
O 1
P 1
Q 1
R 1
S 1
T 1
U 1
V 1
W 1
X 1
Y 1
Z 1
# part-r-00001
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
这个结果跟第0种组合(也就是默认设置)的结果一样而不与第2种组合的结果相同,也就是说如果只设置分组排序,而不设置key排序,那么key排序是不会使用分组排序的函数的,而且还可以推出一个更重要的结论。具体论述如下。
因为我们设置了分组排序,但是从结果来看,分组排序显然没有起到作用,因为那些ASCII码和3的余数相同的key并没有合并到一起,这是为什么呢,其实并不是分组排序没有起到作用,而是分组排序的一个特性,这个特性就是,分组排序时,某一个key先与下一个key做分组排序,如果返回0,则说明这两个key是相同的,就合并到一个迭代器中,然后用下一个key与下下个key做分组排序;如果返回的不是0,则说明这两个key不一样,也就不合并到同一个迭代器中,而且,这个key也不再和下下个key做分组排序了。例如,1的ASCII码是49,与3的余数为1,而2的ASCII码是50,与3的余数为2,这两个key不一样,而且,1也不再和3 4 5 …继续比下去,也就是把 1 单独列出来,作为一个迭代器,而且,key排序之后,key输入的顺序是 1 2 3 4 5 6 7 8 9 ,他们的ASCII码与3的余数与下一个key的余数都是不一样的,所以也就导致了part-r-00001文件中显示的那样。hadoop之所以使用这种机制,是因为本来已经有了key排序,如果你想要把他们分到一组去,你就可以使用key排序把他们分到一起去,再使用分组函数把他们分到一个迭代器中。完全不用让分组排序使用一 一对比的方式来分组,因为这样很浪费资源。
也许你也会想,也许就是分组排序没起到作用呢,所以我做了另一个实验,来证明,如果只设置分组排序,而不设置key排序,那么分组排序还是会起作用的,请看第4中组合。
第四种组合
if (Integer.valueOf(args[2]) == 4) {
System.out.println("======================4=============");
job.setGroupingComparatorClass(MyGroupSort1.class);
}
MyGroupSort1函数只返回0,所以不管传入什么,得到的结果都是,这两个key是一样的,应该分到一个组,加入到一个迭代器中,
运行结果如下:
# part-r-00000
Z 26
# part-r-00001
9 9
看到了把,如果只设置分组排序,不设置key排序,那么分组排序还是会起到作用的。就像这个组合,因为分组排序只返回0,所以会把所有记录分到一个迭代器中,而且 z 和 9都是每组的最后一个,记录个数分别是 26 和 9。所以就产生了以上这种运行结果。
下面可以去看最后一个组合,第6种组合,
第五种组合
if (Integer.valueOf(args[2]) == 5) {
System.out.println("==============5=================");
job.setSortComparatorClass(MySort.class);
job.setGroupingComparatorClass(MyGroupSort2.class);
}
运行结果如下:
# part-r-00000
Z 1
E 1
K 1
H 1
B 1
T 1
Q 1
W 1
N 1
X 1
F 1
C 1
R 1
U 1
I 1
O 1
L 1
A 1
Y 1
V 1
S 1
P 1
M 1
J 1
G 1
D 1
# part-r-00001
6 1
9 1
3 1
1 1
7 1
4 1
5 1
2 1
8 1
这样就跟我们预想的一样了,先看part-r-00001文件,因为 6 9 3的ASCII码分别是 54 57 51,他们的ASCII码与3的余数都是0,而1 7 4的ASCII码分别是 49 55 52, 与3的余数是1,而5 2 8的ASCII码分别是 53 50 56,与3的余数是2,所以就成了上面显示的那种排序方式。part-r-00000文件的排序方式也是如此。如 Z的ASCII码为90,90与3的余数为0,所以排在前面,剩下的那些以此类推。
现在反过来看一下为什么第1种组合所产生的结果。先看part-r-00001文件,他显示的是3 4 8,其中,他们的ASCII码与3的余数分别是 0 1 2。这就说明了一件事,分组排序是使用key排序的算法。例如,6 9 3的ASCII码与3的余数都是0,所以就会合并到同一个迭代器中,这也就是为什么part-r-00001文件中3后面那个是3,也就是迭代器中有三个元素。再例如。1 7 4 的ASCII码与3的余数都是1,所以把他们合并到同一个迭代器中,part-r-00001文件中 4 后面也是一个3。至于 合并之后,key是用谁的key,part-r-00001文件中,6 9 3 里的3,1 7 4里的4 ,5 2 8里的8,结论就是合并之后,用的是整个组的最后一个key。但是我在学习时。老师讲的是,用整个组的最前面的一个key,我也不知道为什么会出现矛盾,如果谁知道,请留言给我,多谢多谢。
part-r-00000文件是这个样子也就可以明白了,Z E K H B T Q W N的ASCII码与3的余数都是0,所以合并到同一个迭代器中,个数有9个,而N是整组的最后一个, part-r-00000文件的第一行为 N 9,而第二行L 8 和第三行 D 9也是这么来的,希望你看明白了,我已经尽了最大努力了。下面你可以看一下第二种组合。
第六种组合
if (Integer.valueOf(args[2]) == 6) {
System.out.println("======================6=============");
job.setSortComparatorClass(MySort1.class);
job.setGroupingComparatorClass(MyGroupSort.class);
}
这个组合其实和第三个组合一样,只不过第三个组合的key排序使用的是程序默认的,而这个组合使用的是自己实现的key排序函数,其实,自己实现的key排序函数跟默认的排序函数一样,都是按照 key的第一个字符的字典顺序升序排列。这个组合的结果如下
# part-r-00000
A 1
B 1
C 1
D 1
E 1
F 1
G 1
H 1
I 1
J 1
K 1
L 1
M 1
N 1
O 1
P 1
Q 1
R 1
S 1
T 1
U 1
V 1
W 1
X 1
Y 1
Z 1
# part-r-00001
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
结果如第3种组合的结果一模一样,我也就不多做解释了,就是证明,默认的key排序函数就是按照 key的字典顺序升序排列。
结论总结
下面是这篇文章所有的结论
1. 默认的key排序和分组排序都是按照 key的字典顺序升序排列。(第0种和第6种组合证明)
2. 当设置了key排序而没有设置分组排序是,分组排序使用key排序的函数。(1,2)
3. 如果只设置分组排序,而不设置key排序,那么key排序是不会使用分组排序的函数的。(3)
4. 分组排序时,某一个key先与下一个key做分组排序,如果返回0,则说明这两个key是相同的,就合并到一个迭代器中,然后用下一个key与下下个key做分组排序;如果返回的不是0,则说明这两个key不一样,也就不合并到同一个迭代器中,而且,这个key也不再和下下个key做分组排序了。(3)
总体代码
public class WordCount {
private static final Log LOG = LogFactory.getLog("MyLog");
public static class MapImpl extends
Mapper<LongWritable, Text, Text, IntWritable> {
enum ERRORROW {
error_field, error_row
}
private static Text k = new Text();
private static IntWritable v = new IntWritable(1);
@Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String valuestr = value.toString();
String[] splits = StringUtils.split(valuestr, ' ');
k.set(splits[0]);
context.write(k, v);
}
}
public static class ReducerImpl extends
Reducer<Text, IntWritable, Text, IntWritable> {
private static IntWritable v = new IntWritable();
@Override
protected void reduce(Text arg0, Iterable<IntWritable> arg1,
Context arg2) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable intWritable : arg1) {
int i = intWritable.get();
sum += i;
}
v.set(sum);
arg2.write(arg0, v);
}
}
static public class myPartition extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key, IntWritable value, int numPartitions) {
char b = key.toString().charAt(0);
if (b >= 'A' && b <= 'Z') {
return 0;
}
if (b >= '0' && b <= '9')
return 1;
return 2;
}
}
/**
* key 排序算法,按照 key的第一个字符与3的余数升序排列
*/
static public class MySort extends WritableComparator {
public MySort() {
super(Text.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
if (a == null || b == null)
return 0;
char c1 = a.toString().charAt(0);
char c2 = b.toString().charAt(0);
return c1 % 3 - c2 % 3;
}
}
/**
* key 排序算法,按照 key的第一个字符的字典顺序升序排列,即默认排序
*/
static public class MySort1 extends WritableComparator {
public MySort1() {
super(Text.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
if (a == null || b == null)
return 0;
char c1 = a.toString().charAt(0);
char c2 = b.toString().charAt(0);
return c1 - c2;
}
}
/**
* 分组 排序算法,key的第一个字符与3的余数相同的key分为一组
*/
static public class MyGroupSort extends WritableComparator {
public MyGroupSort() {
super(Text.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
char c1 = a.toString().charAt(0);
char c2 = b.toString().charAt(0);
return (c1 % 3) - (c2 % 3);
}
}
/**
* 分组 排序算法,直接返回0,即 把所有 记录整合成一个
*/
static public class MyGroupSort1 extends WritableComparator {
public MyGroupSort1() {
super(Text.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
return 0;
}
}
/**
* 分组 排序算法, 按照key的第一个字符是否相等进行分组,即默认分组
*/
static public class MyGroupSort2 extends WritableComparator {
public MyGroupSort2() {
super(Text.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
char c1 = a.toString().charAt(0);
char c2 = b.toString().charAt(0);
return c1 - c2;
}
}
public static void main(String[] args) {
Configuration conf = new Configuration();
try {
Job job = new Job(conf, "Word Count");
job.setJarByClass(WordCount.class);
job.setMapperClass(MapImpl.class);
job.setReducerClass(ReducerImpl.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
Path inpath = new Path(args[0]);
Path outpath = new Path(args[1]);
FileSystem fs = FileSystem.get(conf);
if (!fs.exists(inpath)) {
System.out.println("输入路径 " + args[0] + " 不存在");
}
if (fs.exists(outpath)) {
fs.delete(outpath, true);
}
/*
* 排序算法,有7个不同的搭配组合,通过这些组合可以理解
* mapreduce的排序。
*/
if (Integer.valueOf(args[2]) == 1) {
System.out.println("==============1=================");
job.setSortComparatorClass(MySort.class);
}
if (Integer.valueOf(args[2]) == 2) {
System.out.println("======================2=============");
job.setSortComparatorClass(MySort.class);
job.setGroupingComparatorClass(MyGroupSort.class);
}
if (Integer.valueOf(args[2]) == 3) {
System.out.println("======================3===========");
job.setGroupingComparatorClass(MyGroupSort.class);
}
if (Integer.valueOf(args[2]) == 4) {
System.out.println("======================4=============");
// job.setSortComparatorClass(MySort.class);
job.setGroupingComparatorClass(MyGroupSort1.class);
}
if (Integer.valueOf(args[2]) == 5) {
System.out.println("==============5=================");
job.setSortComparatorClass(MySort.class);
job.setGroupingComparatorClass(MyGroupSort2.class);
}
if (Integer.valueOf(args[2]) == 6) {
System.out.println("======================6=============");
job.setSortComparatorClass(MySort1.class);
job.setGroupingComparatorClass(MyGroupSort.class);
}
/**
* 分区
*/
job.setPartitionerClass(myPartition.class);
job.setNumReduceTasks(3);
FileInputFormat.addInputPath(job, inpath);
FileOutputFormat.setOutputPath(job, outpath);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true) ? 0 : 1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
实验数据
A
1
B
C
D
2
E
F
G
H
3
I
J
4
K
5
6
L
M
N
O
P
Q
R
7
S
T
8
U
V
W
X
Y
Z
9