1、InputFormat
InputFormat是Hadoop平台上Mapreduce输入的规范,仅有两个抽象方法。
- List<InputSplit> getSplits(), 获取由输入文件计算出输入分片(InputSplit),解决数据或文件分割成片问题。
- RecordReader<K,V> createRecordReader(),创建RecordReader,从InputSplit中读取数据,解决读取分片中数据问题。
InputFormat主要能完成下列工作:
1、Validate the input-specification of the job. (首先验证作业的输入的正确性)
2、 Split-up the input file(s) into logical InputSplits, each of which is then assigned to an individual Mapper.(将输入的文件划分成一系列逻辑分片
(InputSplit),一个InputSplit将会被分配给一个独立的MapTask )
3、Provide the RecordReader implementation to be used to glean input records from the logical InputSplit for processing by the Mapper.(提供RecordReader实
现,读取InputSplit中的“K-V对”供Mapper使用)
InputFormat的源代码:
public abstract class InputFormat<K, V> {
/**
* 每个InputSplit的分片被分配到一个独立的Mapper上
* 注:1、这个分片是逻辑上对输入数据进行分片,而实际上输入文件没有被切割成一个个小块。
* 每个分片由输入文件的路径,起始位置,偏移量等
* 2、在InputFormat中创建的RecordReader也要使用InputSplit
*/
public abstract
List<InputSplit> getSplits(JobContext context ) throws IOException, InterruptedException;
/**
* 为每个分片创建一个record reader
*/
public abstract
RecordReader<K,V> createRecordReader(InputSplit split,TaskAttemptContext context)
throws IOException, InterruptedException;
2.InputSplit
Mapper输入的是一个个分片,称InputSplit。在这个抽象类中,是将每个输入分片(Split)中的内容解析成K-V值。
InputSplit的源代码:
public abstract class InputSplit {
/**
* 得到每个分片的大小,可以按照分片大小排序。
*/
public abstract long getLength() throws IOException, InterruptedException;
/**
* Get the list of nodes by name where the data for the split would be local.
* The locations do not need to be serialized.
* 获取存储该分片的数据所在的节点位置
*/
public abstract String[] getLocations() throws IOException, InterruptedException;
}
2.1 下面看看InputSplit的一个子类,FileSplit类:
1 public FileSplit() {}
2
3 /** Constructs a split with host information
4 *
5 * @param file the file name
6 * @param start the position of the first byte in the file to process
7 * @param length the number of bytes in the file to process
8 * @param hosts the list of hosts containing the block, possibly null
9 */
10 public FileSplit(Path file, long start, long length, String[] hosts) {
11 this.file = file;
12 this.start = start;
13 this.length = length;
14 this.hosts = hosts;
15 }
16
17 /** The file containing this split's data. */
18 public Path getPath() { return file; }
19
20 /** The position of the first byte in the file to process. */
21 public long getStart() { return start; }
22
23 /** The number of bytes in the file to process. */
24 @Override
25 public long getLength() { return length; }
26
27 @Override
28 public String toString() { return file + ":" + start + "+" + length; }
29
30
31 // Writable methods
32
33
34 @Override
35 public void write(DataOutput out) throws IOException {
36 Text.writeString(out, file.toString());
37 out.writeLong(start);
38 out.writeLong(length);
39 }
40
41 @Override
42 public void readFields(DataInput in) throws IOException {
43 file = new Path(Text.readString(in));
44 start = in.readLong();
45 length = in.readLong();
46 hosts = null;
47 }
48
49 @Override
50 public String[] getLocations() throws IOException {
51 if (this.hosts == null) {
52 return new String[]{};
53 } else {
54 return this.hosts;
55 }
56 }
从源码中可以看出,FileSplit有四个属性:文件路径,分片起始位置,分片长度和存储分片的hosts。用这四项数据,就可以计算出提供给每个Mapper的分片数据。在InputFormat的getSplit()方法中构造分片,分片的四个属性会通过调用FileSplit的Constructor设置。
2.2再看一个InputSplit的子类:CombineFileSplit。源码如下:
为什么介绍该类呢,因为该类对小文件的处理是很有效的,所有深入理解该类,将有助于该节学习。
上面我们介绍的FileSplit对应的是一个输入文件,也就是说,如果用FileSplit对应的FileInputFormat作为输入格式,那么即使文件特别小,也是作为一个单独的InputSplit来处理,而每一个InputSplit将会由一个独立的Mapper Task来处理。在输入数据是由大量小文件组成的情形下,就会有同样大量的InputSplit,从而需要同样大量的Mapper来处理,大量的Mapper Task创建销毁开销将是巨大的,甚至对集群来说,是灾难性的!
CombineFileSplit是针对小文件的分片,它将一系列小文件封装在一个InputSplit内,这样一个Mapper就可以处理多个小文件。可以有效的降低进程开销。与FileSplit类似,CombineFileSplit同样包含文件路径,分片起始位置,分片大小和分片数据所在的host列表四个属性,只不过这些属性不再是一个值,而是一个列表。
需要注意的一点是,CombineFileSplit的getLength()方法,返回的是这一系列数据的数据的总长度。
现在,我们已深入的了解了InputSplit的概念,看了其源码,知道了其属性。我们知道数据分片是在InputFormat中实现的,接下来,我们就深入InputFormat的一个子类,FileInputFormat看看分片是如何进行的。
3 、FileInputFormat
FileInputFormat中,分片方法代码及详细注释如下,就不再详细解释该方法:
















