Ubuntu16.04 MapReduce WordCount代码详解与eclipse运行方式
从本教程开始熟悉Ubuntu16.04下eclipse的使用,开始我们当然先从入门的WordCount代码入手。
代码详解
一方面为了了解MapReduce的工作流程,另一方面为后续代码的改编应用做准备,下面的源码我们需了解其含义,已加入注释,可以自行阅读:
//package org.apache.hadoop.examples; 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 { public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{ //MapReduce程序需要继承 org.apache.hadoop.mapreduce.Mapper 这个类,并在这个类的继承类中至少自定义实现 Map() 方法,其中 org.apache.hadoop.mapreduce.Mapper 要求的参数有四个(keyIn、valueIn、keyOut、valueOut),即Map()任务的输入和输出都是< key,value >键值对的形式 // 源代码中此处各个参数意义是: // 1、Object:输入< key, value >对的 key 值,此处为文本数据的起始位置的偏移量。在大部分程序下这个参数可以直接使用 Long 类型,源码此处使用Object做了泛化。 // 2、Text:输入< key, value >对的 value 值,此处为一段具体的文本数据。 // 3、Text:输出< key, value >对的 key 值,此处为一个单词。 // 4、IntWritable:输出< key, value >对的 value 值,此处固定为 1 。IntWritable 是 Hadoop 对 Integer 的进一步封装,使其可以进行序列化。 private final static IntWritable one = new IntWritable(1); private Text word = new Text(); // 此处定义了两个变量: // one:类型为Hadoop定义的 IntWritable 类型,其本质就是序列化的 Integer ,one 变量的值恒为 1 。 // word:因为在WordCount程序中,Map 端的任务是对输入数据按照单词进行切分,每个单词为 Text 类型。 public void map(Object key, Text value, Context context)throws IOException, InterruptedException { // 这段代码为Map端的核心,定义了Map Task 所需要执行的任务的具体逻辑实现,map() 方法的参数为 Object key, Text value, Context context,其中: // key: 输入数据在原数据中的偏移量。 // value:具体的数据数据,此处为一段字符串。 // context:用于暂时存储 map() 处理后的结果。 StringTokenizer itr = new StringTokenizer(value.toString()); //StringTokenizer是java工具包中的一个类,用于将字符串进行拆分 while (itr.hasMoreTokens()) { word.set(itr.nextToken());//返回当前位置到下一个分隔符之间的字符串 context.write(word, one);//将word存到容器,记一个数 } //while } //map() // 方法内部首先把输入值转化为字符串类型,并且对Hadoop自带的分词器 StringTokenizer 进行实例化用于存储输入数据。之后对输入数据从头开始进行切分,把字符串中的每个单词切分成< key, value >对的形式,如:< hello , 1>、< world, 1> … } //static class TokenizerMapper public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> { // import org.apache.hadoop.mapreduce.Reducer 类的参数也是四个(keyIn、valueIn、keyOut、valueOut),即Reduce()任务的输入和输出都是< key,value >对的形式。 // 源代码中此处各个参数意义是: // 1、Text:输入< key, value >对的key值,此处为一个单词 // 2、IntWritable:输入< key, value >对的value值。 // 3、Text:输出< key, value >对的key值,此处为一个单词 // 4、IntWritable:输出< key, value >对,此处为相同单词词频累加之后的值。实际上就是一个数字。 private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values,Context context)throws IOException, InterruptedException { // Reduce() 的三个参数为: // 1、Text:输入< key, value >对的key值,也就是一个单词 // 2、value:这个地方值得注意,在前面说到了,在MapReduce任务中,除了我们自定义的map()和reduce()之外,在从map 刀reduce 的过程中,系统会自动进行combine、shuffle、sort等过程对map task的输出进行处理,因此reduce端的输入数据已经不仅仅是简单的< key, value >对的形式,而是一个一系列key值相同的序列化结构,如:< hello,1,1,2,2,3…>。因此,此处value的值就是单词后面出现的序列化的结构:(1,1,1,2,2,3…….) // 3、context:临时存储reduce端产生的结果 int sum = 0;//用sum来记录相同单词的数量,是java类型的数据 for (IntWritable val : values) {// //遍历得到结果 sum += val.get();//因为val的数据类型是IntWritable,所以需要将IntWritable类型的数据转换成java可以识别的数据类型,Intwritable.set(),IntWritable.get()都能进行数据转换,set()方法是将Java数据类型转换成hadoop可识别的数据类型,而get()方法是将hadoop的数据类型转换成java可识别的数据类型 } result.set(sum); //将java的数据类型转换成Hadoop可识别的数据类型 context.write(key, result);//输出结果到hdfs } //reduce } //static class IntSumReducer public static void main(String[] args) throws Exception { Configuration conf = new Configuration();//读取hadoop的配置参数,也就是安装hadoop时候的配置文件例如:core-site.xml、hdfs-site.xml和mapred-site.xml等等文件里的信息 String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();//GenericOptionsParser类,它是用来解释常用hadoop命令,并根据需要为Configuration对象设置相应的值 if (otherArgs.length != 2) { System.err.println("Usage: wordcount <in> <out>");//运行WordCount程序时候一定是两个参数,如果不是就会报错退出,其实这2个参数的作用就是告诉机器输入文件的地址和输出文件的地址 System.exit(2); } Job job = new Job(conf, "word count");//创建一个任务,第二个参数word count是任务的名称 job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class);//装载程序员编写好的计算程序,例如我们的程序类名就是WordCount了。虽然我们编写mapreduce程序只需要实现map函数和reduce函数,但是实际开发我们要实现三个类,第三个类是为了配置mapreduce如何运行map和reduce函数,准确的说就是构建一个mapreduce能执行的job了,例如WordCount类。 job.setCombinerClass(IntSumReducer.class);//设置job的combine阶段的执行类 job.setReducerClass(IntSumReducer.class);//指定要使用的reduce类 job.setOutputKeyClass(Text.class);//设置程序的输出的key值的类型 job.setOutputValueClass(IntWritable.class);//定义输出结果的key/value的类型,也就是最终存储在hdfs上结果文件的key/value的类型 FileInputFormat.addInputPath(job, new Path(otherArgs[0])); //确定输入文件的路径 FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));//确定输出文件的路径 System.exit(job.waitForCompletion(true) ? 0 : 1); //如果job成功运行,我们的程序就会正常退出 } //main() } //class WordCount
eclipse的WordCount实现
此专题继续在Ubuntu下的Hadoop安装与wordcount测试 和Ubuntu16.04下的eclipse安装与Hadoop 3.3.1 MapReduce开发环境配置的基础上进行
一定要记住打开服务先!!!!!!!!
$ cd /usr/local/hadoop $ ./sbin/start-all.sh
首先,不要试图图形化显示DFS Location的内容,不然会持续报错,已经搞了很久,目前还没有找到解决方法,但是需要强调的是hdfs仍然可以通过终端命令行进行创建、上传和删除文件(夹)操作。
第二点,讲一下eclipse如何创建MapReduce java应用程序。
右击创建的工程按如图选择,文件名必须与类名一致,所以在此时必须使用WordCount作为文件名
将源码粘贴到代码区域后,右击文件,按图选择
在配置窗口中,选择Java Application,选择Arguments选项卡,分别填入数据输入和输出目录,两者之间有空格(输入数据提前上传至hdfs,第一节中我们上传了LICENSE.txt到user/hadoop,可仿照那里的操作在命令行进行操作,如下所示)
$ hdfs dfs -mkdir /user $ hdfs dfs -mkdir /user/hadoop $ hdfs dfs -copyFromLocal LICENSE.txt /user/hadoop
需要注意的是,输出路径的最后一级文件夹名需要与其本级目录文件夹名都不同,这是我们自定义的存放结果文件的文件夹,换言之,此文件夹名为系统自动根据用户填写的文件夹名来生成的,如图填写的话,我的user目录下会自动生成文件夹Out来存放结果文件
Apply后Run
可能会出现以下问题
这个问题是日志问题,解决方法为将此文件
复制至
即可解决问题
可能还会有
于是我free -m了一下,发现Swap内存区满了
这个问题是我开机好几天没关的缘故,很简单,只需要重启虚拟机即可
再不出意外的话,就会成功执行,我们可以在localhost:9870找到user/Out/里有我们的结果文件