思路原理介绍
在开始学习Mapreduce程序编写时,通常第一个程序为词频统计。词频统计,顾名思义是统计一篇文档中不同词出现的频数。而本文是在基本词频统计mapreduce程序的基础上进行改进。
在本次实验中,利用mapreduce自带的框架,将每个单词切片,将其设为key,value的值初始为“+”。通过Partitione计算分区,在Combine中将单词的value追加“+”,将其词频增加1倍,最后在Reduce中统计每个单词的词频,并将词频大于或等于3的单词记录,最终输出2倍词频数量的“+”组成的字符串。
改进要求
在实现基本的词频统计的功能基础上,对程序进行以下修改:
- 数据清洗: 对每个词的格式进行规范化(去除不以英文字母开头的所有词)词频小于3次的数据不在结果中显示;
- 结果以有限数量的“+”表示词频统计;
- 自定义一个Combiner类,在其reduce方法中,将相同key的所有值进行相加后乘以2输出;
- 自定义一个Partitioner类和getPartition()方法,将大写字母开头的词分配到一个reducer,将小写字母开头的词分配到另一个reducer;
环境
hadoop:2.9.2
步骤
- 生成键值对:在框架中的map 方法中,读取文档中每个单词,判断其是否以大小写字母开头,若符合条件,将其设为key,且其value为“+”;否则丢弃,不处理。通过该方法,得到键值对的列表。
- 计算分区:在myPartitioner方法中,获取键值对key的值,如果key是大写字母开头,将其分配到属于“0”的reduce中,否则分配到为“1”的reduce中。
- 增加单词的频数:在MyCombiner方法中,获取每个键值对中的value,并将其增加为原来的两倍,并更新原来的value。
- 在IntSumReducer方法中,定义一个StringBuffer类型的sum,统计相同key的键值对,将其value值追加到sum中,最终sum的值为该单词频数的两倍,且sum是以有限个“+”组成的字符串。最后判断sum的长度是否大于或等于6,若满足,则将词频大于3的单词及其sum结果加入到结果集中;否则,丢弃,不记录。
注意:由于在初始化词频时,将初始值设置为2,因此,在后续中过滤词频小于3的词时,过滤的条件修改为小于6.
代码
map部分
从文件中读取每一个词,并检查该词是否为大小写字母开头
private final static Text one = new Text("+");
private Text word = new Text();
public void map(LongWritable key, Text value, Context context
) throws IOException, InterruptedException {
//筛选以字母开头的单词
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
String line = itr.nextToken().toString();
//****itr.nextToken()为string 类型;String line = itr.nextToken()
if((line.charAt(0) >= 65 && line.charAt(0) <= 90) || (line.charAt(0) >= 97 && line.charAt(0) <= 122)){
// ****使用正则表达式,条件可写为:line.substring(0,1).toLowerCase().matches("[a-z]")
word.set(line);
context.write(word, one);
}
}
}
Combiner片段
public static class MyCombiner extends Reducer<Text,Text,Text,Text> {
private Text result = new Text();
public void reduce(Text key, Iterable<Text> values,
Context context
) throws IOException, InterruptedException {
StringBuffer sum = new StringBuffer();
for (Text val : values) {
sum.append(val.toString());
}
result.set(sum.append(sum).toString());
context.write(key, result);
}
}
Partitioner
在partitioner部分,设置分区方式
public class myPartitioner extends Partitioner<Text,Text>{
public int getPartition(Text key, Text value, int numPartitions)
{
String line = key.toString();
if(line.charAt(0) >= 65 && line.charAt(0) <= 90){
return 0;
}
return 1;
}
}
reducer
public static class IntSumReducer
extends Reducer<Text, Text, Text, Text> {
private Text result = new Text();
public void reduce(Text key, Iterable<Text> values,
Context context
) throws IOException, InterruptedException {
StringBuffer sum = new StringBuffer();
for (Text val : values) {
sum.append(val.toString());
}
// 只统计词频>=3的单词
if(sum.length() >= 3){
result.set(sum.toString());
context.write(key, result);
}
}
}