假设需要统计本文文档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,请后续查看我的其他文章)