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 InputSplit
s, 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中,分片方法代码及详细注释如下,就不再详细解释该方法: