import java.io.IOException; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.util.GenericOptionsParser; public class WordCount { /*WordCount里面定义了两个内部类。一个是TokenizerMapper,继承Mapper类*/ public static class TokenizerMapper /*Mapper类给出了4个参数,前两个代表的是map要处理的key和value的类型,默认可以指定Text,Object的范围更加宽泛,也是可以的,这里的key和value,默认采用的是以行为单位,可以理解为行号是key,实际是偏移量,每行的内容是value,词频统计非常有效,后面会说一些特殊情况。*/ /*后两个是要输出的key和value的类型。在实际情况中,我们输出的key是以空格分割的单词,value是1(这个1后面要累加,所以用int,intwritable是hadoop的变量,hadoop要在网络中进行计算传递,所用的数据都是可序列化的)。而且这里面的单词肯定有重复的,不重复统计干啥。map函数的从输入到输出就是对数据进行封装处理。*/ extends Mapper<Object, Text, Text, IntWritable>{ /*定义两个变量,一个是value的,一个是key的。输出的时候就用这两个。*/ private final static IntWritable one = new IntWritable(1); private Text word = new Text(); /*到了实现mapper类里面方法的时候了。map是父类的方法。加@Override更保险,如果这个地方你不小心弄错了,变成自己新的方法,map过程就不会执行了。参数列表中前两个对于map的输入,跟map类的前两个一样。Context是用来封装好输出,交给reduce处理的。*/ public void map(Object key, Text value, Context context ) throws IOException, InterruptedException { /*这里使用到了字符串的分词器,以空格分割,是按照我们输入的每行数据的格式。*/ StringTokenizer itr = new StringTokenizer(value.toString()); /*分词后的数据很像一个迭代器,每次取出一个词,封装成Text类型的key,IntWritable类型的value对输出,这里value是1*/ while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, one); } } } /*reduce的作用就是累加。首先,reduce会收集到具有相同key的key,value对,单词相同的就可以累加,有多少相同的就有多少个1,累加起来就是词数。继承reducer类,四个参数,前两个是map的输出作为reduce的输入,后两个是reduce的输出,输出key是单词,value词数。*/ public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> { private IntWritable result = new IntWritable(); /*参数列表类型照搬上面的前两个,有点区别,这里输入的是具有相同key的键值对的集合,因此是可迭代类型,每个元素的类型是intwritable的。Context作用一样*/ public void reduce(Text key, Iterable<IntWritable> values, Context context ) throws IOException, InterruptedException { /*int sum是用来在这个函数里做累加的,都是相同的key的集合,把value一个一个加起来。最后输出的肯定是序列化的intwritable类型的result,result调用set方法赋值*/ int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } } public static void main(String[] args) throws Exception { /*conf可以理解为在hadoop的conf文件一样的作用。在运行的时候,需要将conf的配置文件拖到src的目录下,否则会报错的,eclipse读取不到hadoop的配置文件下的东西,自身目录下的文件可以读取*/ Configuration conf = new Configuration(); /*输入的参数,input和output目录里的统计用的文件,在eclipse设置。*/ String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); if (otherArgs.length < 2) { System.err.println("Usage: wordcount <in> [<in>...] <out>"); System.exit(2); } /*要运行这个mapreduce的作业就要用到job了。提交这个job就要告诉hadoop我们的jar包,我们用的map和reduce的类是什么,hadoop才会按照我们的指示去执行工作。combiner是一种优化的手段,但并不是都有效,比如求平均值。我们最后得到的结果也是在job的setoutputkeyclass和setoutputvalueclass里面设置。这个与reduce的输出是对应的,写入到part-r-xxxx的文件里。*/ Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); job.setCombinerClass(IntSumReducer.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); for (int i = 0; i < otherArgs.length - 1; ++i) { FileInputFormat.addInputPath(job, new Path(otherArgs[i])); } FileOutputFormat.setOutputPath(job, new Path(otherArgs[otherArgs.length - 1])); /*waitforcomletion的作用就是提交这个作业执行,成功返回1,并退出*/ System.exit(job.waitForCompletion(true) ? 0 : 1); } }
资源地址:http://www.nber.org/patents/ 下的cite75_99.txt
cit"CITING","CITED" 3858241,956203 3858241,1324234 3858241,3398406 3858241,3557384 3858241,3634889 3858242,1515701 3858242,3319261 3858242,3668705
"CITED" "CITING" 1 3964859,4647229 10000 4539112 100000 5031388 1000006 4714284 1000007 4766693 1000011 5033339 1000017 3908629 1000026 4043055 1000033 4190903,4975983 1000043 4091523 1000044 4082383,4055371 1000045 4290571 1000046 5525001,5918892
import java.io.IOException; import java.util.Iterator; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.JobClient; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.Reducer.Context; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; import org.apache.hadoop.util.GenericOptionsParser; import test.WordCount.IntSumReducer; import test.WordCount.TokenizerMapper; public class patent { public static class Map extends Mapper<Text, Text, Text, Text>{ /*直接使用文本来操作,很方便。这里的输入和输出我们都使用上面说的KeyValueTextInputFormat文本预处理。逗号分隔conf.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator", ","),处理的东西都变成文本,又不统计个数。*/ public void map(Text key, Text value, Context context ) throws IOException, InterruptedException { /*这里的map唯一需要做的就是把key和value颠倒过来,我们的目的就是统计被引用的专利有哪些第一列的专利引用,这里的key,value反过来了*/ context.write(value,key); } } public static class Reduce extends Reducer<Text,Text,Text,Text> { /*reducer的方法,第一个是key,唯一键,第二个是value,有很多,之前也说了,reduce收集的是具有相同key的键值对的集合,集合进行了封装,类似于<key1 value1,value2,value3>*/ public void reduce(Text key, Iterable<Text> values, Context context ) throws IOException, InterruptedException { /*接下来,就是把value的字符串连接成以逗号分隔,value1,value2,value3这种类型的形式,这里的转化成了iterator,因为想要next方法来获取数据,iterator比较好操作,这里的操作有点倒退了,毕竟1.x使用的就是iterator,但在传输上,iterator更具有优势。while循环就不说了,很简单。*/ String csv=""; Iterator<Text> iterator=values.iterator(); while (iterator.hasNext()){ if(csv.length()>0)csv+=","; csv+=iterator.next().toString(); } context.write(key, new Text(csv)); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); /*设置每一行的key,value划分都是以逗号分隔*/ conf.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator", ","); Job job = Job.getInstance(conf, "patent"); job.setJarByClass(patent.class); job.setMapperClass(Map.class); /*输入的文本使用的是逗号分隔key,value的方法,输出是text,这个设不设都可以*/ job.setInputFormatClass(KeyValueTextInputFormat.class); job.setOutputFormatClass(TextOutputFormat.class); job.setReducerClass(Reduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); /*沿用了1.x的方法,个人觉得使用这个看起来比2.x那个简洁多*/ Path in = new Path(args[0]); Path out = new Path(args[1]); FileInputFormat.setInputPaths(job, in); FileOutputFormat.setOutputPath(job, out); /*执行,输出结果上面已经给了*/ System.exit(job.waitForCompletion(true)?0:1); } }
我的出现了几个问题。在把core、hdfs和log配置文件拷到src目录下的时候,是可以运行在hadoop上的。运行在yarn上需要四个配置文件都拷贝,但是还是出现了找不到内部类的错误。后来直接将工程导出为jar包,使用/bin/yarn命令运行是没有问题的。好像是eclipse缺失yarn的jar,暂时未解决。