使用java编写MR的单词统计wordcount

假设需要统计本文文档a.txt
内容如下:
I love china
I love java
I love U

1、总共需要三个类,一个执行主类,一个继承Mapper类,一个继承Reduce类

一、配置pom.xml

新建一个maven工程
maven依赖如下:

			<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
			  <modelVersion>4.0.0</modelVersion>
			  <groupId>com.qiu</groupId>
			  <artifactId>wordCount</artifactId>
			  <version>0.0.1-SNAPSHOT</version>
			  <packaging>jar</packaging>
			  <name>wordCount</name>
			  <url>http://maven.apache.org</url>
			  <properties>
			  	<junit.version>4.11</junit.version>
			  	<hadoop.version>2.9.1</hadoop.version>
			  	<java.tools>C:\Program Files\Java\jdk1.8.0_181\lib\tools.jar</java.tools>
			  	<!-- <java.tools>/usr/lib/jvm/java-7-oracle-cloudera/lib/tools.jar</java.tools> -->
			  	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
			  </properties>
			
			  <dependencies>
			  <!-- 依赖hadoop的相关jar包 开始================   -->
				<dependency>
				    <groupId>org.apache.hadoop</groupId>
				    <artifactId>hadoop-common</artifactId>
				    <version>${hadoop.version}</version>
				</dependency>
				<dependency>
				    <groupId>org.apache.hadoop</groupId>
				    <artifactId>hadoop-hdfs</artifactId>
				    <version>${hadoop.version}</version>
				</dependency>
				<dependency>
				    <groupId>org.apache.hadoop</groupId>
				    <artifactId>hadoop-mapreduce-client-core</artifactId>
				    <version>${hadoop.version}</version>
				</dependency>
				<dependency>
				    <groupId>org.apache.hadoop</groupId>
				    <artifactId>hadoop-mapreduce-client-common</artifactId>
				    <version>${hadoop.version}</version>
				</dependency>
				<!-- 依赖hadoop的相关jar包 结束================   -->
				<dependency>
				    <groupId>jdk.tools</groupId>
				    <artifactId>jdk.tools</artifactId>
				    <version>1.8</version>
				    <scope>system</scope>
				    <systemPath>${java.tools}</systemPath>
			  	</dependency>
			  	<dependency>
				    <groupId>junit</groupId>
				    <artifactId>junit</artifactId>
				    <version>${junit.version}</version>
				    <scope>test</scope>
			    </dependency>
			  </dependencies>
			  <build>
				<plugins> 
					<plugin>
					<!-- 使用maven插件进行打包   -->
						<groupId>org.apache.maven.plugins</groupId>
						<artifactId>maven-assembly-plugin</artifactId>
						<version>3.1.0</version>
						<configuration>
							<archive>
								<manifest>
									<mainClass>com.qiu.combiner.WCCombinerApp</mainClass>
								</manifest>
							</archive>
							<descriptorRefs>
								<descriptorRef>jar-with-dependencies</descriptorRef>
							</descriptorRefs>
						</configuration>
						<executions>
							<execution>
								<id>make-assembly</id>
								<phase>package</phase>
								<goals>
									<goal>single</goal>
								</goals>
							</execution>
						</executions>
					</plugin>
				</plugins>
			  </build>
			</project>

二、Mapper类

主要是遍历读取每行的静态数据源,将其输出为(单词,数字)

(0, I love china)(1,I love java)(2,I love U)—Mapper—>(I,1)(love,1)(china,1)(I,1)(love,1)(java,1)…
所以,在继承Mapper中的泛型应为<LongWritable, Text, Text, IntWritable>
上面4个分别为keyIn,ValueIn,KeyOut,ValueOut,同时重写map方法

代码如下:

		import java.io.IOException;
		import org.apache.hadoop.io.IntWritable;
		import org.apache.hadoop.io.LongWritable;
		import org.apache.hadoop.io.Text;
		import org.apache.hadoop.mapreduce.Mapper;
		//继承Mapper类默认给到泛型为<KEYIN, VALUEIN, KEYOUT, VALUEOUT>,即输入k,v;输出k,v
		//因为Mapper类主要是遍历读取每行的静态数据源,将其输出为(单词,数字)
		//所以,在继承Mapper中的泛型hadoop自带且对应的数据类型<LongWritable, Text, Text, IntWritable>
		public class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
			@Override
			//重写Mapper类中的map方法,key为传入的读取的每行下标index,Text为传入的文本,而context作用是调用context.write输出结果
			protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
					throws IOException, InterruptedException {
				// 将传进来的文本数据(即Text对象)转成java的字符串类型
				String line = value.toString();
				//此时数据转化为-->		0  I love china
				// 因为I love china的间隔是空格,所有通过空格" "分隔开每个单词,形成数组
				String[] words = line.split(" ");
				//空格分割后Text(value)转化为---->[I,love,china]
				// 遍历分割后的数组,同时将每个单独的单词使用write方法丢出去
				// 因为是在hadoop中传递,需要使用hadoop自带的数据类型为:<Text,IntWritable>
				for(String word : words) {
					context.write(new Text(word), new IntWritable(1));
				}
				// 此时,I love china数据转换为如下:
				// (I 1)
				// (love 1)
				// (china 1)
			}
		}

三、Reducer类

接受由上一级(Mapper)传出的数据

  • 因为这里上一级(Mapper)传出的数据类型为<Text,IntWritable>

  • 所以此类的传入数据为<Text,IntWritable,Text,IntWritable>

  • 跟上面Mapper一样默认给到泛型为<KEYIN, VALUEIN, KEYOUT, VALUEOUT>,即输入k,v;输出k,v

  • 而前两个就对应上一级传入数据,又因为Reducer类是负责将数据统计汇总,这里设置输出格式为<Text,IntWritable>

    数据传输变化流程为:
    (I,[1,1,1])(love,[1])(china,[1])…—Reducer—>(I,3)(love,1)…

    而Mapper传出的明明是:(I,1)(love,1)(china,1)(I,1)(love,1)(java,1)…
    为什么到了Reducer就变成(I,[1,1,1])(love,[1])(china,[1])…了?
    因为在Mapper和Reducer之间还经过了一步Shuffling(分组)的操作,即:
    (I,[1,1,1])(love,[1])(china,[1])…–Shuffling–>(I,[1,1,1])(love,[1])(china,[1])…再传递到Reducer

     代码如下:
    
			import java.io.IOException;
			import org.apache.hadoop.io.IntWritable;
			import org.apache.hadoop.io.Text;
			import org.apache.hadoop.mapreduce.Reducer;
			//继承Reducer类默认给到泛型为<KEYIN, VALUEIN, KEYOUT, VALUEOUT>,即输入k,v;输出k,v
			//因为Reducer类主要是负责统计汇总上一级传来的数据,将其输出为(单词,数字)
			//所以,在继承Reducer中的泛型hadoop自带且对应的数据类型<Text, IntWritable, Text, IntWritable>
			public class WCReduce extends Reducer<Text, IntWritable, Text, IntWritable>{
				@Override
				//重写Reducer类中的reduce方法,key为传入的Text即单词,values为传入的分组后的单词数字,而context作用是调用context.write输出结果
				protected void reduce(Text key, Iterable<IntWritable> values,
						Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
					// I 1 love 1 china 1
					// 数据经过分组后,传入进来的结果为:(I  [1,1,1])(love  [1])(china  [1])
					// 起一个总和count
					Integer count = 0; // 3  I 3
					// 遍历每个传递进来的数字数组,然后将其数量进行相加,
					// 假设values为[2,5,8] 遍历后---->count=2+5+8=15
					for(IntWritable value : values) {
						// IntWritable自带的get()返回int类型,相当于将IntWritable转换成int
						count += value.get();
					}
					// 将每个单独的单词使用write方法丢出去
					// 因为是在hadoop中传递,需要使用hadoop自带的数据类型为:<Text,IntWritable>
					context.write(key, new IntWritable(count));
					// 此时,I love china数据转换为如下:
					// (I 3)
					// (love 1)
					// (china 1)
				}
			}

四、设置一个main主类入口

调用对应方法,并将Mapper和Reducer设置进去

	代码如下:
			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.lib.input.FileInputFormat;
			import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
			
			public class App 
			{
			    public static void main( String[] args ) throws Exception
			    {
			    	// TODO 自动生成的方法存根
			 		Configuration conf = new Configuration();
			 		// job 表示一个MR的任务流程
			 		Job job = Job.getInstance(conf,"wordCount");
			 		// job 怎么运行呢?设置执行MR的main入口
			 		job.setJarByClass(App.class);
			 		// 设置Mapper类
			 		job.setMapperClass(WCMapper.class);
			 		// 设置Reducer类
			 		job.setReducerClass(WCReduce.class);
			 		// 设置Combiner类 此条可加可不加
			 		job.setCombinerClass(WCReduce.class);
			 		// 设置Mapper 输出的K,v格式
			 		job.setMapOutputKeyClass(Text.class);
			 		job.setMapOutputValueClass(IntWritable.class);
			 		// 设置输出的K,v格式
			 		job.setOutputKeyClass(Text.class);
			 		job.setOutputValueClass(IntWritable.class);
			 		// 设置读取的文本数据源路径,args[0] 表示写入命令时的第一个参数路径
			 		FileInputFormat.setInputPaths(job, new Path(args[0]));
			 		// 设置输出的路径,args[1] 表示流程结束后输出到哪个路径下
			 		FileOutputFormat.setOutputPath(job,new Path(args[1]));
			 		// 表示如果此流程失败,则打印内容
			 		boolean b = job.waitForCompletion(true);
			 		if(!b) {
			 			System.err.println("this task failed!!");
			 		}
			    }
			}

五、对此项目进行打包

  • 项目右键run as 执行maven build ---->Goals上输入package,进行打包

注:此处存在报错可能,Failed to execute goal org.apache.maven.plugins:maven-assembly-plugin:3.1.0:
错误原因为:jar没下载全。自行补全,找到maven下载的仓库,删掉对应路径的包,重新下载即可。

  • 通过xftp等传入Linxu,然后执行命令 hadoop jar jar包名 /文本数据源路径(eg:/upload/a.txt)
    /输出的路径(eg:/result)

  • 比如笔者命令为: hadoop jar wordCount-sort.jar /upload/a.txt /result
    第一个路径需要先将a.txt的文本文件先行上传到hadoop的/upload下面

(前提是已经在Linxu上安装好了hadoop并启动,关于Linxu安装ubuntu,请后续查看我的其他文章)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值