MapReduce学习笔记和总结(一)

 

目录

第一章 MapReduce

1.1 用Java写一个WordCount(单词统计)程序

1.1.1 统计一个文件中,每个单词出现的次数

1.1.2 统计多个文件中,每个单词出现的总次数

1.2 用MapReduce框架编写WordCount

1.2.1 序列化 & 反序列化

1.2.2 继承Mapper类(Map端)

1.2.3 继承Reducer类(Reduce端)

1.2.4 驱动类(代码提交类)

1.3 总结MapReduce的编程套路


第一章 MapReduce

之前介绍了Hadoop中的HDFS,现在对Hadoop中的MapReduce进行介绍,MapReduce是一个分布式计算编程框架,之所以称之为框架,是因为它帮助我们封装了许多底层的实现,比如,读取文件等。这些操作都不需要我们实现,我们只需要将注意力放在处理业务,编写业务代码即可。

我们以最常用的单词统计为例,体验一下MapReduce的方便。单词统计就是统计一篇文章中,各个单词出现的次数。我分别通过两种方式实现,一是自己写Java代码,二是使用MapReduce

测试用例:

hello	name    tom
hello	rose	hello	hadoop
hello	jack	rose	hadoop

 

1.1 用Java写一个WordCount(单词统计)程序

1.1.1 统计一个文件中,每个单词出现的次数

编程思路:

1)创建输入流,对文件进行按行读取

2)创建容器,存放读取的单词;该容器应该大小可变,推荐使用:Map<单词,次数>

3)按行读取数据时,将每行中的每个单词放入Map中。如果不存在,则以该单词为Key,以1为value,添加到Map中;如果存在,将value值取出,加一后更新该word的value;

代码实现:

package com.ethan.mr;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;

public class WordCount01 {

	public static void main(String[] args) {
		
		try {
			// 创建一个流:对文件进行读取
			// 创建一个容器:存放读取的单词;容器大小可变,Map<单词,次数>
			BufferedReader br = new BufferedReader(new FileReader("F:\\mrtest\\word.txt"));
			Map<String, Integer> map = new HashMap<String, Integer>();
			// 文件进行读取,并向map中添加
			String line = null;
			while((line = br.readLine()) != null) {
				// 将读取到的一行内容进行拆分
				String[] split = line.split("\t");
				// 将数据中的每个单词放入Map中
				for (String word : split) {
					// 如果不存在,则以wword为key,1为value,添加到map中
					if(!map.containsKey(word)) {
						map.put(word, 1);
					}else {
						// 如果存在,将value值取出 + 1
						int value = map.get(word) + 1;
						map.put(word, value);
					}
				}
			}
			System.out.println(map);	
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}

1.1.2 统计多个文件中,每个单词出现的总次数

编程思路:分而治之

1)每一次统计一个文件的单词出现的次数,与1.1.1中一样

2)最后将第一步中的结果进行合并

代码实现:

package com.ethan.mr;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class WordCount02 {

	public static void main(String[] args) {
		// 1.分别统计
		Map<String, Integer> map01 = readOneFile("F:\\mrtest\\word01.txt");
		Map<String, Integer> map02 =readOneFile("F:\\mrtest\\word02.txt");
		Map<String, Integer> map03 =readOneFile("F:\\mrtest\\word03.txt");
		Map<String, Integer> map04 =readOneFile("F:\\mrtest\\word04.txt");
		Map<String, Integer> map05 =readOneFile("F:\\mrtest\\word05.txt");
		Map<String, Integer> map06 =readOneFile("F:\\mrtest\\word06.txt");
		
		System.out.println(map01);
		System.out.println(map02);
		System.out.println(map03);
		System.out.println(map04);
		System.out.println(map05);
		System.out.println(map06);
		// 2.合并
Map<String,Integer>mergerResult=mergerResult(map01,map02,map03,map04,map05,map06);
		System.out.println("最终统计:" + mergerResult);
	}
	
	/**
	 * 统计单个文件中的单词个数
	 * @param filePath
	 * @return
	 */
	public static Map<String, Integer>readOneFile(String filePath) {
		Map<String, Integer> map = new HashMap<String, Integer>();
		try {
			// 创建一个流:对文件进行读取
			// 创建一个容器:存放读取的单词;容器大小可变,Map<单词,次数>
			BufferedReader br = new BufferedReader(new FileReader(filePath));
			// 文件进行读取,并向map中添加
			String line = null;
			while((line = br.readLine()) != null) {
				// 将读取到的一行内容进行拆分
				String[] split = line.split("\t");
				// 将数据中的每个单词放入Map中
				for (String word : split) {
					// 如果不存在,则以wword为key,1为value,添加到map中
					if(!map.containsKey(word)) {
						map.put(word, 1);
					}else {
						// 如果存在,将value值取出 + 1
						int value = map.get(word) + 1;
						map.put(word, value);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return map;
	}
	/**
	 * 文件合并
	 * 循环遍历每一个map集合,将最终结果放在一个新的map中
	 * @param map
	 * @return
	 */
	public static Map<String,Integer> mergerResult(Map<String, Integer> ... maps) {
		
		Map<String,Integer> resultMap = new HashMap<String, Integer>();
		// 对可变参数进行循环遍历,获取到每一个map
		for (Map<String, Integer> m : maps) {
			
			// 循环遍历每一个Map,将结果放到结果Map中
			for(Entry<String, Integer> kv : m.entrySet()) {
				// 取出Entry中的每一个key进行判断
				// 如果不存在,存到新的Map中
				String key = kv.getKey();
				int value = kv.getValue();
				if(!resultMap.containsKey(key)) {
					resultMap.put(key, value);
				}else {// 如果存在 hello-8 hello-4 原始的value取出来 + 新的value值
					Integer valueNew = resultMap.get(key) + value;
					resultMap.put(key, valueNew);
				}
			}
		}
		return resultMap;
	}
}

根据以上例子发现,如果进行多个文件数据的统计,我们的实现思路和代码框架,都是相同的。所以可以将这种解决问题的框架提取出来,用户只需要根据自己的业务逻辑进行实现就可以。

在以上例子中,readOneFile、mergerResult分别简单模拟了MapReduce计算框架。readOneFile()方法可以看成map();mergerResult()方法可以看成reduce();我们可以初步的理解为Map:分部分统计;Reduce:结果合并。

1.2 用MapReduce框架编写WordCount

MapReduce中已经为我们封装好了Mapper类和Reducer类。如果我们有自己的业务逻辑,只需要继承相应的类,并重写其中的map和reduce方法即可。

1.2.1 序列化 & 反序列化

因为MR处理的文件分布在不同的节点中,处理数据时必然经过持久化磁盘和网络传输的过程,所以数据必须经过序列化和反序列化。

Java中的序列化接口是Serializable,是连同类进行序列化的,过于累赘。所以,Hadoop弃用Java中的序列化接口,而提供了一套轻便的Writable接口,只对值进行序列化。Hadoop中提供的数据类型是已经实现Writable接口的。

Java基本数据类型与Hadoop基本数据类型的对应关系:(部分)

intIntWritable
longLongWritable
doubleDoubleWritable
FloatFloatWritable
StringText
NullNullWritable

Java中的数据类型转化为Hadoop中的数据类型,只需要new Hadoop数据类型(Java中的值)即可。

Hadoop类型转为Java类型用get()方法即可。

String javaString = "hello";
// 将Java中String类型转为Hadoop中对应的Text类型
Text hadoopText = new Text(javaString);

//  将Hadoop中Text类型转为Java中对应的String类型
String javaExchange = hadoopText.get();

1.2.2 继承Mapper类(Map端)

public class WordCountMapper extends Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {}

1)介绍Mapper类中的四个泛型

Mapper的输入,就是需要统计的文件;这个文件是以一行一行的方式提供给使用者。(底层调用类似9.1.1中的br.line)。

① KEYIN:Map端输入的Key的泛型,这里指的是每一行的起始偏移量。

MR底层实现文件输入依赖字节流。当读取到\r\n时,代表读取一行结束。

② VALUEIN:Map端输入的值的类型,这里指的是一行的内容。

③ KEYOUT:Map端输出的Key的类型,这里是指的是单词。

④ VALUEOUT:Map端输出的value类型,这里指的是标记1,便于统计。

2)继承Mapper类后,重写其map方法

map方法的调用频率:每读取完一行调用一次

方法参数:

   LongWritable key:每一行的起始偏移量

   Text value:每一行的内容(每次读取的那一行内容)

   Context context:内容对象(上下文对象),用于传输的对象,发送给Reduce方法

package com.ethan.mr;

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;

/**
 * 分部统计
 * @author Ethan
 *
 */
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

	/**
	 * 方法调用频率:一行调用一次
	 * LongWritable key:每一行的起始偏移量
	 * Text value:每一行的内容(每次读取的那一行内容)
	 * Context context:内容对象(上下文对象),用于传输的对象,发送给Reduce
	 */
	@Override
	protected void map(LongWritable key, Text value, Context context)
			throws IOException, InterruptedException {
		// 获取到每一行的内容,将每个单词加标签
		String line = value.toString();
		// 进行每个单词的切分
		String[] words = line.split("\t");
		// 循环遍历打标记
		for(String w : words) {
			context.write(new Text(w), new IntWritable(1));
		}
	}
}

1.2.3 继承Reducer类(Reduce端)

Reducer类处理的是Mapper端输出的结果,即reduce方法的输入,是map方法的输出。

public class WordCountReducer extends Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>{}

1)Reducer类中的四个泛型

① KEYIN:Reducer端输入的key的类型,是Mapper端输出的key的类型;本例是Text类型。

② VALUEIN:Reducer端输入的value的类型,是Mapper端输出的value的类型;本例是IntWritable类型。

③ KEYOUT:Reducer统计结果的key的类型,这里指的最终统计完成的单词(Text类型)

④ VALUEOUT:Reducer统计结果的value的类型,这里是指单词出现的总次数(IntWritable类型)

2)继承Reducer类,重写reduce方法

reduce方法的调用频率:每一组调用一次,key相同的为一组

Mapper端的输出结果由Context.write()方法发送至Reducer端,本例中的数据格式应为:<hello,1>、<word,1>、<hello,1>...<Java,1>等。

Mapper端输出的数据到达Reducer端之前,MR框架内部帮助我们对数据进行了整理,叫分组。默认的分组是按照map输出的key进行分组,即key相同的为一组。Mapper端有多少不同的key,就有多少组。分组的详细介绍:

方法参数:

Text key:每一组中相同的key

Iterable<IntWritable> values:每一组中相同的key对应的所有value值;这里代表hello <1,1,…,1,1>

Context context:内容对象(上下文对象);可将数据写出到HDFS

package com.ethan.mr;

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

/**
 * 合并单词出现的次数
 * @author Ethan
 *
 */
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{

	/**
	 * Text key:每一组中的相同的那个key
	 * Iterable<IntWritable> values:每一组中的所有value值
	 * Context context:内容对象(上下文对象),用于传输,写出到HDFS
	 */
	@Override
	protected void reduce(Text key, Iterable<IntWritable> values, Context context)
			throws IOException, InterruptedException {
		int count = 0; // 计数
		// 统计结果,循环遍历values
		for (IntWritable i : values) {	
			int value = i.get(); // hadoop数据类型转为java数据类型
			count = value + count;
		}
		context.write(key, new IntWritable(count));
	}
}

1.2.4 驱动类(代码提交类)

在MapReduce中,一个计算程序叫做一个Job,用于封装计算程序中的Mapper和Reducer,以及输入和输出。

驱动类的编写步骤:

①加载配置文件

②创建一个Job,并在Job中分别设置:主驱动类、Mapper和Reducer类、设置Mapper的输出类型、设置Reducer的输出类型、设置输入路径和输出路径等信息。

输入路径:HDFS中需要统计单词的文件的路径

输出路径:最终统计结果输出的路径;注意:输出路径一定不能存在!

③Job提交

代码实现:

package com.ethan.mr;

import java.io.IOException;

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;

/**
 * 驱动类:代码提交类
 * @author Ethan
 */
public class Driver {

	public static void main(String[] args) {

		try {
			// 加载配置文件
			Configuration conf = new Configuration();
			// 启动一个Job 
			Job job = Job.getInstance(conf);
			// 设置程序的主驱动类,运行的时候打成Jar包运行
			job.setJarByClass(Driver.class);
			// 设置Mapper和Reducer类
			job.setMapperClass(WordCountMapper.class);
			job.setReducerClass(WordCountReducer.class);
			// 设置Mapper的输出类型
			job.setMapOutputKeyClass(Text.class);
			job.setMapOutputValueClass(IntWritable.class);
			// 设置Reducer的输出类型 
			job.setOutputKeyClass(Text.class);
			job.setOutputValueClass(IntWritable.class);

			// 设置输入,输出路径 (addInputPath和setInputPath都行)
			// args[0]:代表运行代码时,控制台手动输入的参数
			// 输入路径:需要统计单词的路径
			FileInputFormat.addInputPath(job, new Path(args[0]));

			// 输出路径:最终统计结果输出的路径 
			// 注意:输出路径一定不能存在
			FileOutputFormat.setOutputPath(job, new Path(args[1]));
		// job提交,true:执行打印日志
			job.waitForCompletion(true);
		} catch (ClassNotFoundException | InterruptedException | IOException e) {
			e.printStackTrace();
		}
	}
}

最后将代码打成Jar包,在集群中运行;运行指令:Hadoop jar xxx.jar driver所在包 输入路径 输出路径

1.3 总结MapReduce的编程套路

 在MR程序中分为map阶段(WordCountMapper)和reduce阶段(WordCountReducer)。

①map阶段:主要进行取出数据,进行切分,打标签,发送给reduce

②map至reduce见的中间过程:分组

③reduce阶段:对map的输出结果进行合并  

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值