我们的需求是想统计一个文件中用IK分词后每个词出现的次数,然后按照出现的次数降序排列。也就是高频词统计。
由于hadoop在reduce之后就不能对结果做什么了,所以只能分为两个job完成,第一个job统计次数,第二个job对第一个job的结果排序。 第一个job的就是hadoop最简单的例子countwords,我要说的是用hadoop对结果排序。 假设第一个job的结果输出如下:
part-r-0000文件内容:
a 5
b 4
c 74
d 78
e 1
r 64
f 4
要做的就是按照每个词出现的次数降序排列。
1 |
**********************************分割线***************************************** |
首先可能会出现这样的问题:
1.可能上一个job为多个reduce,也就是会产生多个结果文件,因为一个reduce就会生成一个结果文件,结果存放在上一个job输出目录下类似part-r-00的文件里。
2.需要排序的文件内容很大,所以需要考虑多个reduce的情况。
1 |
*********************************分割线******************************* |
怎么去设计mapreduce
1.在map阶段按照行读取文本,然后调用map方法时把上一个job的结果颠倒,也就是map后结果应该是这样的
5 |
......................... |
2.然后map后,hadoop会对结果进行分组,这时结果就会变成
(5:a)
(4:b,f)
(74:c)
3.然后按照reduce数目的大小自定义分区函数,让结果形成多个区间,比如我认为大于50的应该在一个区间,一共3个reduce,那么最后的数据应该是三个区间,大于50的直接分到第一个分区0,25到50之间的分到第二个分区1,小于25的分到第三个分区2.因为分区数和reduce数是相同的,所以不同的分区对应不同的reduce,因为分区是从0开始的,分区是0的会分到第一个reduce处理,分区是1的会分到第2个reduce处理,依次类推。并且reduce对应着输出文件,所以,第一个reduce生成的文件就会是part-r-0000,第二个reduce对应的生成文件就会是part-r-0001,依次类推,所以reduce处理时只需要把key和value再倒过来直接输出。这样最后就会让形成数目最大的字符串就会在第一个生成文件里,排好的序就会文件命的顺序。
代码如下:
1 |
*******************************分割线***************************************** |
map:
02 |
*
把上一个mapreduce的结果的key和value颠倒,调到后就可以按照key排序了。 |
04 |
*
@author zhangdonghao |
07 |
public class SortIntValueMapper extends |
08 |
Mapper<LongWritable,
Text, IntWritable, Text> { |
09 |
private final static IntWritable
wordCount = new IntWritable(1); |
11 |
private Text
word = new Text(); |
13 |
public SortIntValueMapper()
{ |
18 |
public void map(LongWritable
key, Text value, Context context) |
19 |
throws IOException,
InterruptedException { |
21 |
StringTokenizer
tokenizer = new StringTokenizer(value.toString()); |
22 |
while (tokenizer.hasMoreTokens())
{ |
23 |
word.set(tokenizer.nextToken().trim()); |
24 |
wordCount.set(Integer.valueOf(tokenizer.nextToken().trim())); |
25 |
context.write(wordCount,
word); |
reudce:
03 |
*
@author zhangdonghao |
06 |
public class SortIntValueReduce extends |
07 |
Reducer<IntWritable,
Text, Text, IntWritable> { |
09 |
private Text
result = new Text(); |
12 |
public void reduce(IntWritable
key, Iterable<Text> values, Context context) |
13 |
throws IOException,
InterruptedException { |
14 |
for (Text
val : values) { |
15 |
result.set(val.toString()); |
16 |
context.write(result,
key); |
Partitioner:
02 |
*
按照key的大小来划分区间,当然,key 是 int值 |
04 |
*
@author zhangdonghao |
07 |
public class KeySectionPartitioner<K,
V> extends Partitioner<K,
V> { |
09 |
public KeySectionPartitioner()
{ |
13 |
public int getPartition(K
key, V value, int numReduceTasks)
{ |
15 |
*
int值的hashcode还是自己本身的数值 |
21 |
if (numReduceTasks
> 1 &&
key.hashCode() < maxValue) { |
22 |
int sectionValue
= maxValue / (numReduceTasks - 1); |
24 |
while ((key.hashCode()
- sectionValue * count) > sectionValue) { |
27 |
keySection
= numReduceTasks - 1 -
count; |
Comparator:
04 |
*
@author zhangdonghao |
07 |
public class IntKeyAscComparator extends WritableComparator
{ |
09 |
protected IntKeyAscComparator()
{ |
10 |
super(IntWritable.class, true); |
15 |
public int compare(WritableComparable
a, WritableComparable b) { |
16 |
return -super.compare(a,
b); |
job的关键设置:
02 |
*
这里是map输出的key和value类型 |
04 |
job.setOutputKeyClass(IntWritable.class); |
05 |
job.setOutputValueClass(Text.class); |
07 |
job.setMapperClass(SortIntValueMapper.class); |
09 |
job.setReducerClass(SortIntValueReduce.class); |
11 |
job.setSortComparatorClass(IntKeyAscComparator.class); |
13 |
job.setPartitionerClass(KeySectionPartitioner.class); |
15 |
job.setInputFormatClass(TextInputFormat.class); |
16 |
job.setOutputFormatClass(TextOutputFormat.class); |
18 |
*这里可以放输入目录数组,也就是可以把上一个job所有的结果都放进去 |
20 |
FileInputFormat.setInputPaths(job,
inputPath); |
22 |
FileOutputFormat.setOutputPath(job,outputPath); |
大概就是这样,亲测可用。(^__^)