首页 > 技术文章 > Ubuntu16.04 MapReduce WordCount代码详解与eclipse运行方式

yuxiaohan1236 2021-10-07 00:03 原文

从本教程开始熟悉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/里有我们的结果文件

推荐阅读