文件参数 传递 到 job中呢?
在Client 中调用FileInputFormat.addInputPath(job, path);
addInputPath
的主要作用为将文件路径加载到了Conf中 :
``` public static void addInputPath(Job job, Path path)
throws IOException
{
Configuration conf = job.getConfiguration();
path = path.getFileSystem(conf).makeQualified(path);
String dirStr = StringUtils.escapeString(path.toString());
String dirs = conf.get("mapred.input.dir");
conf.set("mapred.input.dir", dirs + "," + dirStr);
}```
其中FileInputFormat
实现了 InputFormat
接口。接口的实际含义是Hadoop用来接收客户端输入参数的。所有输入格式都继承了InputFormat
的格式。InputFormat
是一个抽象的类,其子类中含有用来读取普通文件的FileInputFormat
:其作用为读取数据库的DBInputFormat
。
public List<InputSplit> getSplits(JobContext job)throws IOException
{
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
List<InputSplit> splits = new ArrayList();
List<FileStatus> files = listStatus(job);
for (FileStatus file : files) {
Path path = file.getPath();
FileSystem fs = path.getFileSystem(job.getConfiguration());
long length = file.getLen();
BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0L, length);
if ((length != 0L) && (isSplitable(job, path))) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining = length;
while (bytesRemaining / splitSize > 1.1D) {
int blkIndex = getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(new FileSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0L) {
splits.add(new FileSplit(path, length - bytesRemaining,
bytesRemaining, blkLocations[(blkLocations.length - 1)].getHosts()));
}
}
else if (length != 0L) {
splits.add(new FileSplit(path, 0L, length, blkLocations[0].getHosts()));
}
else {
splits.add(new FileSplit(path, 0L, length, new String[0]));
}
job.getConfiguration().setLong("mapreduce.input.num.files", files.size());
LOG.debug("Total # of splits: " + splits.size());
return splits;
}
在 InputFormat
接口中 有getSplits
方法,也就是说分片操作实际上实在 map之前 就已经做好了。
- 计算分片大小:最小值
minSize
和最大值maxSize
。可以通过设置mapred.min.split.size
和mapred.max.split.size
来设置。 splits
链表用来存储计算得到的输入分片,files
则存储作为由listStatus()
获取的输入文件列表。然后对于每个输入文件,判断是否可以分割,通过computeSplitSize
计算出分片大小splitSize
。
- 其计算方法是:
Math.max(minSize,Math.min(maxSize,blockSize));
也就是保证在minSize
和maxSize
之间,且如果minSize<=blockSize<=maxSize
,则设为blockSize。然后我们根据这个splitSize计算出每个文件的inputSplits集合,然后加入分片列表splits中。 - 注意到我们生成InputSplit的时候按上面说的使用文件路径,分片起始位置,分片大小和存放这个文件的hosts列表来创建。最后我们还设置了输入文件数量:mapreduce.input.num.files。
- 其计算方法是:
分片有时怎么传递给 map
我们使用了 就是InputFormat中的另一个方法createRecordReader() 这个方法将已经分片的样本
public abstract RecordReader<K, V> createRecordReader(InputSplit paramInputSplit, TaskAttemptContext paramTaskAttemptContext) throws IOException, InterruptedException;
RecordReader是用来从一个输入分片中读取一个一个的K -V 对的抽象类,我们可以将其看作是在InputSplit上的迭代器。我们从API接口中可以看到它的一些方法,最主要的方法就是nextKeyvalue()方法,由它获取分片上的下一个K-V 对。
public RecordReader() {}
public abstract void initialize(InputSplit paramInputSplit, TaskAttemptContext paramTaskAttemptContext)
throws IOException, InterruptedException;
public abstract boolean nextKeyValue()
throws IOException, InterruptedException;
public abstract KEYIN getCurrentKey()
throws IOException, InterruptedException;
public abstract VALUEIN getCurrentValue()
throws IOException, InterruptedException;
public abstract float getProgress()
throws IOException, InterruptedException;
public abstract void close()
throws IOException;
在 InputFormat 构建一个 RecordReader 出来,然后调用RecordReader initialize 的方法,初始化RecordReader 对象,将已经分好的分片文件,以Key-Value 的方式放到RecordReader中。
在写Mapper时,需要继承Mapper.class,可能
- 重写Map()方法:map()每次接受一个K-V对,然后我们对这个K-V对进行处理,再分发出处理后的数据。
- 重写setup()方法:以对这个map task进行一些预处理,比如创建一个List之类的。
重写cleanup()方法:对做一些处理后的工作,也可能在cleanup()中写出K-V对。
•setup(),此方法被MapReduce框架仅且执行一次,在执行Map任务前,进行相关变量或者资源的集中初始化工作。若是将资源初始化工作放在方法map()中,导致Mapper任务在解析每一行输入时都会进行资源初始化工作,导致重复,程序运行效率不高!
•cleanup(),此方法被MapReduce框架仅且执行一次,在执行完毕Map任务后,进行相关变量或资源的释放工作。若是将释放资源工作放入方法map()中,也会导致Mapper任务在解析、处理每一行文本后释放资源,而且在下一行文本解析前还要重复初始化,导致反复重复,程序运行效率不高!
例如:
举个例子就是:InputSplit的数据是一些整数,然后我们要在mapper中算出它们的和。我们就可以在先设置个sum属性,然后map()函数处理一个K-V对就是将其加到sum上,最后在cleanup()函数中调用context.write(key,value);
Mapper.class 中的run 方法,相当与map task 的驱动
public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context)
throws IOException, InterruptedException
{
setup(context); //进行初始操作
try {
while (context.nextKeyValue()) { //获取的K-V对
map(context.getCurrentKey(), context.getCurrentValue(), context); //进行Map处理
}
} finally {
cleanup(context); //进行cleanup处理
}
}
}