Wordcount是hadoop的入门程序,类似其他程序语言的hello world程序一般。这个程序简短,但是不简单。通过多种方式实现,加强对mapreduce理解,大有好处。下面是最近我学习hadoop总结,利用wordcount把Secondary sort、In Map aggregation、Task wordflow串起来,供以后查阅。
实现方法1:常规方式计算wordcount
在Map对每行文本分割出单词,Reduce对单词统计频次。代码略。
实现方法2(其实代码是错误的,但很经典):输出结果按单词频次排序。自定义单词类实现,类属性包括单词名称、频次等,其中compareTo按频次排序,按照单词名称group。
代码实现如下:
1、定义单词类Word,继承WritableComparable,具有单词名称、单词频次等两个属性。其中,compareTo实现按照单词频次对Word类进行排序。
<span style="font-size:14px;">public class Word implements WritableComparable {
String wordname;//单词名称,String类型
int wordfreq;//单词频次,int类型
public int compareTo(Object arg0) {
//compreTo函数实现Word类按照单词频次排序
Word wcother=(Word)arg0;
return wordfreq-wcother.getWordfreq();
}
(省略get/set/toString等代码)
}</span>
2、定义Group Comparator。在compare中按照单词名称对比来实现Word类group。
<span style="font-size:14px;">public static class Wcgroup extends WritableComparator {
protected Wcgroup() {
super(Word.class,true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
Word lhs = (Word)a;
Word rhs = (Word)b;
return lhs.getWordname.compareTo(rhs.getWordname);
}
}</span>
3、定义map。按照空格对输入文本按行分割,规整单词名称,创建Word实例(单词频次为1)并输出。
<span style="font-size:14px;">public static class Wcordermap extends Mapper<LongWritable, Text, Word, IntWritable> {
Word outputKey ;
IntWritable outputValue = new IntWritable(1);
int index=1;
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String []words = StringUtils.split(value.toString(), ' ');
for(String thisword : words){
if(thisword.endsWith(".")||thisword.endsWith(",")||thisword.endsWith(":")||thisword.endsWith("!")||thisword.endsWith("?")){
//规整单词名称(剔除末尾句号、逗号、冒号、感叹号、问号等)
thisword=thisword.substring(0, thisword.length()-1);
}
outputKey=new Word(thisword,1);//创建Word实例(单词频次为1)并输出。
context.write(outputKey, outputValue);
}
}
}</span>
4、定义reduce。按照按Word统计每个group中的数量,把结果作为该单词频次放入对应属性。
<span style="font-size:14px;">public static class Wcorderreduce extends Reducer<Word, IntWritable, Word, IntWritable> {
IntWritable outputValue = new IntWritable(0);
protected void reduce(Word key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sumcount=0;
while(values.iterator().hasNext()){
sumcount++;
values.iterator().next();
}
key.setWordfreq(sumcount);//按Word统计每个group中的数量,把结果作为该单词频次放入对应属性。
outputValue.set(sumcount);
context.write(key, outputValue);
}
}</span>
程序输入:文本文件“a b c a a”
程序输出:(a,1), (b,1),(c,1),(a,2)
存在问题:单词“a”没有完全group,第1个a和第4、5个a没有group在一起。
问题原因:group comparator没有“全局”观点,不是将一个reduce中所有对比结果相等的记录都放在一起。而是只对比排序后数据的当前这条下一条。如果不相等,就认为下一条应该放到另外一个group中,并触发reduce调用把当前group记录传入。具体见http://wenda.chinahadoop.cn/question/2579。
解决方法:无法通过修改compareTo实现wordcount按照频次输出。因为这会影响group。
实现方法3:输出结果按照单词频次排序。通过两次mapreduce实现。第1次mapreduce即方法1中的数据,完成(单词名称,单词频次)统计。第2次mapreduce对第1次计算结果进行排序。
主要代码如下:
1、按照方法1,计算wordcount,输出(单词名称,单词频次)结果,存入hdfs。这里略。
2、对步骤1的结果,再次执行map,把单词频次作为key,单词名称作为value,进行输出。
<span style="font-size:14px;">public static class Wc2map extends Mapper<LongWritable, Text, IntWritable, Text> {
protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException {
String []words=StringUtils.split(value.toString(), '\t’); \\前一个mapreduce输出,用\t进行分列
if(words.length<2||words[0].isEmpty()){ \\避免读入”_SUCCESS”空文件
return;
}
Text outputValue=new Text(words[0]); \\单词名称作为value
IntWritable outputKey=new IntWritable(Integer.parseInt(words[1]));\\单词频次作为key
context.write(outputKey, outputValue);\\完成输出
}
}</span>
3、执行默认Reducer,输入(单词频次,单词名称),输出按单词频次排序的结果。
<span style="font-size:14px;">job2.setReducerClass(Reducer.class);</span>
程序输入:文本文件“a b c a a”
程序输出:第1步mapreduce,输出(a,3),(b,1),(c,1)。第2步mapreduce,输出(1, b),(1,c),(3,a)。
需要注意:第2步mapreduce,单纯执行map,设置setNumReduce(0),无法实现单词频次排序。
实现方法4:输出结果按照单词频次排序。通过PriorityQueue实现。把mapreduce输出的最终(单词名称,单词频次),存入PriorityQueue,按照单词频次排序。
主要代码如下:
1、定义Word类,继承WritableComparable,compareTo按照单词频次进行排序,equals按照单词名称进行比较是否相等。
<span style="font-size:14px;">public class Word implements WritableComparable<Word> {
private String wordname;
private int wordfreq;
public int compareTo(Word o) {
return o.getWordfreq() - wordfreq;
}
public void incresefreq(){
wordfreq++;
}
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Word oldword = (Word)obj;
return wordname.equals(oldword.getWordname());
}
(省略get/set/toString等方法)
}</span>
2、定义Mapper类。利用单词名称,构建Word类,并放到ArrayList中存储。如果该Word类在ArrayList中不存在,则直接插入ArrayList。如果已存在,则把wordfreq加1。
<span style="font-size:14px;">public static class Wcinmapmapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private ArrayList<Word> allword;
private PriorityQueue<Word> queue;
private Text outputKey = new Text();
private IntWritable outputValue = new IntWritable();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String []words = StringUtils.split(value.toString().trim(), ' ');
for(String thisword : words){
if(thisword.endsWith(".")||thisword.endsWith(",")||thisword.endsWith(":")||thisword.endsWith("!")||thisword.endsWith("?")){
thisword=thisword.substring(0, thisword.length()-1);
}
Word wordclass = new Word(thisword, 1); //构造Word类。
int wordindex = allword.indexOf(wordclass); //判断Word类在ArrayList是否存在。如果存在则返回索引,否则返回-1。需要注意,判断是否Word类存在,使用了equals。
if(wordindex==-1){
allword.add(wordclass); //Word类不存在,直接插入ArrayList。
}else{
wordclass=allword.get(wordindex); //Word类已存在,则wordfreq加1,并把这个Word重新覆盖ArrayList对应位置。
wordclass.incresefreq();
allword.set(wordindex, wordclass);
}
}
}</span>
3、在Mapper的cleanup中,把ArrayList写入PriorityQueue中,实现Word类按照wordfreq进行排序。
<span style="font-size:14px;">protected void cleanup( Context context) throws IOException, InterruptedException {
queue = new PriorityQueue<Word>();
queue.addAll(allword); //创建PriorityQueue,把ArrayList全部放进去,并对全部Word类按照compareTo进行排序。
for(int i=1;i<=queue.size();i++){
Word tail=queue.poll(); //从顶部返回Word类,并写入hdfs
if(tail!=null){
outputKey.set(tail.getWordname());
outputValue.set(tail.getWordfreq());
context.write(outputKey, outputValue);
}
}
}
protected void setup(
Context context)
throws IOException, InterruptedException {
allword = new ArrayList<Word>();
}
}</span>
程序输入:文本文件“a b c a a”
程序输出:输出(b,1),(c,1),(a,3)
需要注意:对于大数据量情况,使用PriorityQueue可能导致内存占用大的问题。