Mapper类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.hadoop.mapreduce;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Stable;
@Public
@Stable
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Mapper() {
}
protected void setup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
protected void map(KEYIN key, VALUEIN value, Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
context.write(key, value);
}
protected void cleanup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
this.setup(context);
try {
while(context.nextKeyValue()) {
this.map(context.getCurrentKey(), context.getCurrentValue(), context);
}
} finally {
this.cleanup(context);
}
}
public abstract class Context implements MapContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Context() {
}
}
}
Mapper类主要有五种方法:
- protected void
setup
(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) - protected void
map
(KEYIN key, VALUEIN value, Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) - protected void
cleanup
(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) - public void
run
(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) - public abstract class
Context
implements MapContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
1. setup()
protected void setup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
初始时调用,用来加载一些初始化的工作,像全局文件、建立数据库的链接等等,执行一次。
2. cleanup()
protected void cleanup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
结束时调用,收尾工作,如关闭文件、关闭数据库连接、执行map()后的键值分发等等,执行一次。
3. map()
protected void map(KEYIN key, VALUEIN value, Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
context.write(key, value);
}
它继承Mapper类,是一个需要重点重写的方法,将map工作的处理逻辑都要放在这个方法中。
map方法有三个参数,Mapper有四个泛型变量KEYIN, VALUEIN, KEIOUT, VALUEOUT
,它们分别代表输入的键值类型和输出的键值类型,context是上下文,用于保存Job的配置信息、状态和map处理结果,用于最后的write方法。
map中有重要的四个方法
context.nextKeyValue();
负责读取数据,但是方法的返回值却不是读取到的key-value,而是返回了一个标识有没有读取到数据的布尔值context.getCurrentKey();
负责获取context.nextKeyValue() 读取到的keycontext.getCurrentValue();
负责获取context.nextKeyValue() 读取到的valuecontext.write(key,value);
负责输出mapper阶段输出的数据
4. run()
public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
this.setup(context);
try {
while(context.nextKeyValue()) {
this.map(context.getCurrentKey(), context.getCurrentValue(), context);
}
} finally {
this.cleanup(context);
}
}
run方法中先执行setup
函数,然后是用map
处理数据,当处理完数据后用cleanup
的收尾工作。值得注意的是,setup函数和cleanup函数由系统作为回调函数只做一次,并不像map函数那样执行多次。
实现的是设计模式中的模板方法模式。Mapper类中定义了一些方法,用户可以继承这个类重写方法以应对不同的需求,但是这些方法内部的执行顺序是确定好的。它封装了程序的算法,让用户能集中精力处理每一部分的具体逻辑。
run方法在程序执行中会默认调用,从他的执行流程来看也给常符合我们的预期,先进行初始化,如果还有输入的数据,那么调用map方法处理每一个键值对,最终执行结束方法。
模板方法模式:
模板模式中分基本方法和模板方法,上述的run可看做基本方法,需要调用其它方法实现整体逻辑,不会被修改,其它几个方法可看做模板方法。基本方法一般会用final修饰,保证其不会被子类修改,而模板方法使用protect修饰,表示需要在子类中实现。
此外,模板模式中还有一个叫钩子方法的概念,即给子类一个授权,允许子类通过重写钩子方法来颠覆基本逻辑的执行,这有时候是非常有用的,可以用来完善具体的逻辑。
5. Context
public abstract class Context implements MapContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Context() {
}
}
Context类,它实现了MapContext接口,而MapContext继承于TaskInputOutputContext。context为上下文,用于暂时存储job的配置信息、状态和 map() 处理后的结果等。
Reducer
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.hadoop.mapreduce;
import java.io.IOException;
import java.util.Iterator;
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Stable;
import org.apache.hadoop.mapreduce.ReduceContext.ValueIterator;
import org.apache.hadoop.mapreduce.task.annotation.Checkpointable;
@Checkpointable
@Public
@Stable
public class Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Reducer() {
}
protected void setup(Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
protected void reduce(KEYIN key, Iterable<VALUEIN> values, Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
Iterator i$ = values.iterator();
while(i$.hasNext()) {
VALUEIN value = i$.next();
context.write(key, value);
}
}
protected void cleanup(Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
public void run(Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
this.setup(context);
try {
while(context.nextKey()) { //这个地方调用ReduceContextImpl的方法进行判断
this.reduce(context.getCurrentKey(), context.getValues(), context);
Iterator<VALUEIN> iter = context.getValues().iterator();
if (iter instanceof ValueIterator) {
((ValueIterator)iter).resetBackupStore();
}
}
} finally {
this.cleanup(context);
}
}
public abstract class Context implements ReduceContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Context() {
}
}
}
Reducer中的类的方法与Mapper类相同,重点区别主要是在reduce方法的参数,它将map处理后的结果作为它的输入参数。在执行到while(context.nextKey())
时,会将各个相同的key对应的value组合成一个list,称为the list of values,<key, (list of vlaues)>。大致的实现方式:由于经过Map处理后数据已有序排列的方式传递给Reduce,Reduce通过获取下一个key进行判断,如果为相同的Key,就将其放入对应的list列表中;如果为不同的Key说明此时的Key所拥有的Value已经全部放入列表中,进行下一个Key的list组建。如果下一个Key为空,说明对Map进行Reduce的处理全部完成。
Hadoop的迭代器(Iterable values)中使用了对象重用,即迭代时value始终指向一个内存地址(引用值始终不变)改变的是引用指向的内存地址中的数据。
while (context.nextKey())
这个地方调用ReduceContextImpl
的方法进行判断。
public class ReduceContextImpl {
private RawKeyValueIterator input;//这个迭代器里面存储的key-value对元素。
private KEYIN key; // current key
private VALUEIN value; // current value
private boolean firstValue = false; // first value in key
private boolean nextKeyIsSame = false; // more w/ this key
private boolean hasMore; // more in file
private ValueIterable iterable = new ValueIterable();//访问自己的内部类
public ReduceContextImpl() throws InterruptedException, IOException{
hasMore = input.next();//对象创建的时候,就先判断reduce接收的key-value迭代器是否有元素,并获取下一个元素
}
/** 创建完成就调用该方法 ,开始处理下一个唯一的key*/
public boolean nextKey() throws IOException,InterruptedException {
while (hasMore && nextKeyIsSame) {
//判断迭代器是否还有下一个元素已经下一个元素是否和上一个已经遍历出来的key-value元素的key是不是一样
nextKeyValue();
}
if (hasMore) {
if (inputKeyCounter != null) {
inputKeyCounter.increment(1);
}
return nextKeyValue();
} else {
return false;
}
}
/**
* Advance to the next key/value pair.
*/
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if (!hasMore) {
key = null;
value = null;
return false;
}
firstValue = !nextKeyIsSame;
//获取迭代器下一个元素的key
DataInputBuffer nextKey = input.getKey();
//设置当前key的坐标
currentRawKey.set(nextKey.getData(), nextKey.getPosition(),
nextKey.getLength() - nextKey.getPosition());
buffer.reset(currentRawKey.getBytes(), 0, currentRawKey.getLength());
//反序列化得到当前key对象
key = keyDeserializer.deserialize(key);
//获取迭代器下一个元素的value
DataInputBuffer nextVal = input.getValue();
buffer.reset(nextVal.getData(), nextVal.getPosition(), nextVal.getLength()
- nextVal.getPosition());
//反序列化value
value = valueDeserializer.deserialize(value);
currentKeyLength = nextKey.getLength() - nextKey.getPosition();
currentValueLength = nextVal.getLength() - nextVal.getPosition();
if (isMarked) {
//存储下一个key和value
backupStore.write(nextKey, nextVal);
}
//迭代器向下迭代一次
hasMore = input.next();
//如果还有元素,则进行比较,判断key是否相同
if (hasMore) {
nextKey = input.getKey();
//这个地方也是比较关键的:
nextKeyIsSame = comparator.compare(currentRawKey.getBytes(), 0,
currentRawKey.getLength(),
nextKey.getData(),
nextKey.getPosition(),
nextKey.getLength() - nextKey.getPosition()
) == 0;
} else {
nextKeyIsSame = false;
}
inputValueCounter.increment(1);
return true;
}
//一个迭代器模式的内部类
protected class ValueIterator implements ReduceContext.ValueIterator<VALUEIN> {
private boolean inReset = false;
private boolean clearMarkFlag = false;
@Override//它并不仅仅是判断迭代器是否还有下一个元素,而且还要判断下一个元素和上一个元素是不是相同的key
public boolean hasNext() {
if (inReset && backupStore.hasNext()) {
return true;
}
return firstValue || nextKeyIsSame;
}
@Override
//这个地方要注意了,其实在获取下一个元素的时候主要调用的是nextKeyValue();
public VALUEIN next() {
if (inReset) {
if (backupStore.hasNext()) {
backupStore.next();
DataInputBuffer next = backupStore.nextValue();
buffer.reset(next.getData(), next.getPosition(), next.getLength()
- next.getPosition());
value = valueDeserializer.deserialize(value);
return value;
} else {
inReset = false;
backupStore.exitResetMode();
if (clearMarkFlag) {
clearMarkFlag = false;
isMarked = false;
}
}
}
// if this is the first record, we don't need to advance
if (firstValue) {
firstValue = false;
return value;
}
// otherwise, go to the next key/value pair
nextKeyValue();//该方法就是获取下一个key,value对,key值的变化也就在这里表现出来了。
return value;
}
}
//内部类,实现迭代器,具备迭代器功能
protected class ValueIterable implements Iterable<VALUEIN> {
private ValueIterator iterator = new ValueIterator();
@Override
public Iterator<VALUEIN> iterator() {
return iterator;
}
}
public Iterable<VALUEIN> getValues() throws IOException, InterruptedException {
return iterable;
}
}
ReduceContextImpl
类的RawKeyValueIterator input
迭代器对象里面存储中着key-value对的元素, 以及一个只存储value的迭代器,然后每调一次我们实现的reduce方法,就是传入ValueIterable
迭代器对象和当前的key。但是我们在方法里面调用迭代器的next方法时,其实调用了nextKeyValue
,来获取下一个key和value,并判断下一个key是否和 上一个key是否相同,然后决定hashNext
方法是否结束,同时对key进行了一次重新赋值。
这个方法获取KV的迭代器的下一个KV值,然后把K值和V值放到之前传入我们自己写的Reduce类的方法中哪个输入参数的地址上,白话说:框架调用我们写的reduce方法时,传入了三个参数,然后我们方法内部调用phoneNbrs.hashNext
方法就是调用的ReduceContextImpl
的内部类ValueIterator的hashNext
方法,这个方法里面调用了ReduceContextImpl
内的nextKeyValue
方法,该方法内部又清除了之前调用用户自定义reduce方法时传入的k,v参数的内存地址的数据,然后获取了RawKeyValueIterator input
迭代器的下一个KV值,然后把k值和V值放入该数据。