平时我们写MapReduce程序的时候,在设置输入格式的时候,总会调用形如job.setInputFormatClass(KeyValueTextInputFormat.class);来保证输入文件按照我们想要的格式被读取。所有的输入格式都继承于InputFormat,这是一个抽象类,其子类有专门用于读取普通文件的FileInputFormat,用来读取数据库的DBInputFormat等等。

不同的InputFormat都会按自己的实现来读取输入数据并产生输入分片,一个输入分片会被单独的map task作为数据源。下面我们先看看这些输入分片(inputSplit)是什么样的。

InputSplit:

我们知道Mappers的输入是一个一个的输入分片,称InputSplit。InputSplit是一个抽象类,它在逻辑上包含了提供给处理这个InputSplit的Mapper的所有K-V对。

  1. public abstract class InputSplit {
  2. public abstract long getLength() throws IOException, InterruptedException;
  3. public abstract
  4. String[] getLocations() throws IOException, InterruptedException;
  5. }

getLength()用来获取InputSplit的大小,以支持对InputSplits进行排序,而getLocations()则用来获取存储分片的位置列表。
  我们来看一个简单InputSplit子类:FileSplit。

  1. public class FileSplit extends InputSplit implements Writable {
  2. private Path file;
  3. private long start;
  4. private long length;
  5. private String[] hosts;
  6. FileSplit() {}
  7. public FileSplit(Path file, long start, long length, String[] hosts) {
  8. this.file = file;
  9. this.start = start;
  10. this.length = length;
  11. this.hosts = hosts;
  12. }
  13. //序列化、反序列化方法,获得hosts等等……
  14. }

从上面的源码我们可以看到,一个FileSplit是由文件路径,分片开始位置,分片大小和存储分片数据的hosts列表组成,由这些信息我们就可以从输入文件中切分出提供给单个Mapper的输入数据。这些属性会在Constructor设置,我们在后面会看到这会在InputFormat的getSplits()中构造这些分片。

我们再看CombineFileSplit:

  1. public class CombineFileSplit extends InputSplit implements Writable {
  2. private Path[] paths;
  3. private long[] startoffset;
  4. private long[] lengths;
  5. private String[] locations;
  6. private long totLength;
  7. public CombineFileSplit() {}
  8. public CombineFileSplit(Path[] files, long[] start,
  9. long[] lengths, String[] locations) {
  10. initSplit(files, start, lengths, locations);
  11. }
  12. public CombineFileSplit(Path[] files, long[] lengths) {
  13. long[] startoffset = new long[files.length];
  14. for (int i = 0; i < startoffset.length; i++) {
  15. startoffset[i] = 0;
  16. }
  17. String[] locations = new String[files.length];
  18. for (int i = 0; i < locations.length; i++) {
  19. locations[i] = "";
  20. }
  21. initSplit(files, startoffset, lengths, locations);
  22. }
  23. private void initSplit(Path[] files, long[] start,
  24. long[] lengths, String[] locations) {
  25. this.startoffset = start;
  26. this.lengths = lengths;
  27. this.paths = files;
  28. this.totLength = 0;
  29. this.locations = locations;
  30. for(long length : lengths) {
  31. totLength += length;
  32. }
  33. }
  34. //一些getter和setter方法,和序列化方法
  35. }

与FileSplit类似,CombineFileSplit同样包含文件路径,分片起始位置,分片大小和存储分片数据的host列表,由于CombineFileSplit是针对小文件的,它把很多小文件包在一个InputSplit内,这样一个Mapper就可以处理很多小文件。要知道我们上面的FileSplit是对应一个输入文件的,也就是说如果用FileSplit对应的FileInputFormat来作为输入格式,那么即使文件特别小,也是单独计算成一个输入分片来处理的。当我们的输入是由大量小文件组成的,就会导致有同样大量的InputSplit,从而需要同样大量的Mapper来处理,这将很慢,想想有一堆map task要运行!!这是不符合Hadoop的设计理念的,Hadoop是为处理大文件优化的。

最后介绍TagInputSplit,这个类就是封装了一个InputSplit,然后加了一些tags在里面满足我们需要这些tags数据的情况,我们从下面就可以一目了然。

  1. class TaggedInputSplit extends InputSplit implements Configurable, Writable {
  2. private Class<? extends InputSplit> inputSplitClass;
  3. private InputSplit inputSplit;
  4. @SuppressWarnings("unchecked")
  5. private Class<? extends InputFormat> inputFormatClass;
  6. @SuppressWarnings("unchecked")
  7. private Class<? extends Mapper> mapperClass;
  8. private Configuration conf;
  9. //getters and setters,序列化方法,getLocations()、getLength()等
  10. }

现在我们对InputSplit的概念有了一些了解,我们继续看它是怎么被使用和计算出来的。

InputFormat:

通过使用InputFormat,MapReduce框架可以做到:

1、验证作业的输入的正确性

2、将输入文件切分成逻辑的InputSplits,一个InputSplit将被分配给一个单独的Mapper task

3、提供RecordReader的实现,这个RecordReader会从InputSplit中正确读出一条一条的K-V对供Mapper使用。

  1. public abstract class InputFormat<K, V> {
  2. public abstract
  3. List<InputSplit> getSplits(JobContext context
  4. ) throws IOException, InterruptedException;
  5. public abstract
  6. RecordReader<K,V> createRecordReader(InputSplit split,
  7. TaskAttemptContext context
  8. ) throws IOException,
  9. InterruptedException;
  10. }

上面是InputFormat的源码,getSplits用来获取由输入文件计算出来的InputSplits,我们在后面会看到计算InputSplits的时候会考虑到输入文件是否可分割、文件存储时分块的大小和文件大小等因素;而createRecordReader()提供了前面第三点所说的RecordReader的实现,以将K-V对从InputSplit中正确读出来,比如LineRecordReader就以偏移值为key,一行的数据为value,这就使得所有其createRecordReader()返回了LineRecordReader的InputFormat都是以偏移值为key,一行数据为value的形式读取输入分片的。

FileInputFormat:

PathFilter被用来进行文件筛选,这样我们就可以控制哪些文件要作为输入,哪些不作为输入。PathFilter有一个accept(Path)方法,当接收的Path要被包含进来,就返回true,否则返回false。可以通过设置mapred.input.pathFilter.class来设置用户自定义的PathFilter。

  1. public interface PathFilter {
  2. boolean accept(Path path);
  3. }

FileInputFormat是InputFormat的子类,它包含了一个MultiPathFilter,这个MultiPathFilter由一个过滤隐藏文件(名字前缀为'-'或'.')的PathFilter和一些可能存在的用户自定义的PathFilters组成,MultiPathFilter会在listStatus()方法中使用,而listStatus()方法又被getSplits()方法用来获取输入文件,也就是说实现了在获取输入分片前先进行文件过滤。

  1. private static class MultiPathFilter implements PathFilter {
  2. private List<PathFilter> filters;
  3. public MultiPathFilter(List<PathFilter> filters) {
  4. this.filters = filters;
  5. }
  6. public boolean accept(Path path) {
  7. for (PathFilter filter : filters) {
  8. if (!filter.accept(path)) {
  9. return false;
  10. }
  11. }
  12. return true;
  13. }
  14. }

这些PathFilter会在listStatus()方法中用到,listStatus()是用来获取输入数据列表的。

下面是FileInputFormat的getSplits()方法,它首先得到分片的最小值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。

  1. public List<InputSplit> getSplits(JobContext job
  2. ) throws IOException {
  3. long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
  4. long maxSize = getMaxSplitSize(job);
  5. // generate splits
  6. List<InputSplit> splits = new ArrayList<InputSplit>();
  7. List<FileStatus>files = listStatus(job);
  8. for (FileStatus file: files) {
  9. Path path = file.getPath();
  10. FileSystem fs = path.getFileSystem(job.getConfiguration());
  11. long length = file.getLen();
  12. BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);
  13. if ((length != 0) && isSplitable(job, path)) {
  14. long blockSize = file.getBlockSize();
  15. long splitSize = computeSplitSize(blockSize, minSize, maxSize);
  16. long bytesRemaining = length;
  17. while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
  18. int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
  19. splits.add(new FileSplit(path, length-bytesRemaining, splitSize,
  20. blkLocations[blkIndex].getHosts()));
  21. bytesRemaining -= splitSize;
  22. }
  23. if (bytesRemaining != 0) {
  24. splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,
  25. blkLocations[blkLocations.length-1].getHosts()));
  26. }
  27. } else if (length != 0) {
  28. splits.add(new FileSplit(path, 0, length, blkLocations[0].getHosts()));
  29. } else {
  30. //Create empty hosts array for zero length files
  31. splits.add(new FileSplit(path, 0, length, new String[0]));
  32. }
  33. }
  34. // Save the number of input files in the job-conf
  35. job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
  36. LOG.debug("Total # of splits: " + splits.size());
  37. return splits;
  38. }
  39. //……setters and getters

就这样,利用FileInputFormat 的getSplits方法,我们就计算出了我们的作业的所有输入分片了。

那这些计算出来的分片是怎么被map读取出来的呢?就是InputFormat中的另一个方法createRecordReader(),FileInputFormat并没有对这个方法做具体的要求,而是交给子类自行去实现它。
RecordReader
  RecordReader是用来从一个输入分片中读取一个一个的K -V 对的抽象类,我们可以将其看作是在InputSplit上的迭代器。我们从类图中可以看到它的一些方法,最主要的方法就是nextKeyvalue()方法,由它获取分片上的下一个K-V 对。

我们再深入看看上面提到的RecordReader的一个子类:LineRecordReader。

LineRecordReader由一个FileSplit构造出来,start是这个FileSplit的起始位置,pos是当前读取分片的位置,end是分片结束位置,in是打开的一个读取这个分片的输入流,它是使用这个FileSplit对应的文件名来打开的。key和value则分别是每次读取的K-V对。然后我们还看到可以利用getProgress()来跟踪读取分片的进度,这个函数就是根据已经读取的K-V对占总K-V对的比例来显示进度的。

  1. public class LineRecordReader extends RecordReader<LongWritable, Text> {
  2. private static final Log LOG = LogFactory.getLog(LineRecordReader.class);
  3. private CompressionCodecFactory compressionCodecs = null;
  4. private long start;
  5. private long pos;
  6. private long end;
  7. private LineReader in;
  8. private int maxLineLength;
  9. private LongWritable key = null;
  10. private Text value = null;
  11. //我们知道LineRecordReader是读取一个InputSplit的,它从InputSplit中不断以其定义的格式读取K-V对
  12. //initialize函数主要是计算分片的始末位置,以及打开想要的输入流以供读取K-V对,输入流另外处理分片经过压缩的情况
  13. public void initialize(InputSplit genericSplit,
  14. TaskAttemptContext context) throws IOException {
  15. FileSplit split = (FileSplit) genericSplit;
  16. Configuration job = context.getConfiguration();
  17. this.maxLineLength = job.getInt("mapred.linerecordreader.maxlength",
  18. Integer.MAX_VALUE);
  19. start = split.getStart();
  20. end = start + split.getLength();
  21. final Path file = split.getPath();
  22. compressionCodecs = new CompressionCodecFactory(job);
  23. final CompressionCodec codec = compressionCodecs.getCodec(file);
  24. // open the file and seek to the start of the split
  25. FileSystem fs = file.getFileSystem(job);
  26. FSDataInputStream fileIn = fs.open(split.getPath());
  27. boolean skipFirstLine = false;
  28. if (codec != null) {
  29. in = new LineReader(codec.createInputStream(fileIn), job);
  30. end = Long.MAX_VALUE;
  31. } else {
  32. if (start != 0) {
  33. skipFirstLine = true;
  34. --start;
  35. fileIn.seek(start);
  36. }
  37. in = new LineReader(fileIn, job);
  38. }
  39. if (skipFirstLine) {  // skip first line and re-establish "start".
  40. start += in.readLine(new Text(), 0,
  41. (int)Math.min((long)Integer.MAX_VALUE, end - start));
  42. }
  43. this.pos = start;
  44. }
  45. public boolean nextKeyValue() throws IOException {
  46. if (key == null) {
  47. key = new LongWritable();
  48. }
  49. key.set(pos); //对于LineRecordReader来说,它以偏移值为key,以一行为value
  50. if (value == null) {
  51. value = new Text();
  52. }
  53. int newSize = 0;
  54. while (pos < end) {
  55. newSize = in.readLine(value, maxLineLength,
  56. Math.max((int)Math.min(Integer.MAX_VALUE, end-pos),
  57. maxLineLength));
  58. if (newSize == 0) {
  59. break;
  60. }
  61. pos += newSize;
  62. if (newSize < maxLineLength) {
  63. break;
  64. }
  65. // line too long. try again
  66. LOG.info("Skipped line of size " + newSize + " at pos " +
  67. (pos - newSize));
  68. }
  69. if (newSize == 0) {
  70. key = null;
  71. value = null;
  72. return false;
  73. } else {
  74. return true;
  75. }
  76. }
  77. @Override
  78. public LongWritable getCurrentKey() {
  79. return key;
  80. }
  81. @Override
  82. public Text getCurrentValue() {
  83. return value;
  84. }
  85. /**
  86. * Get the progress within the split
  87. */
  88. public float getProgress() {
  89. if (start == end) {
  90. return 0.0f;
  91. } else {
  92. return Math.min(1.0f, (pos - start) / (float)(end - start));//读取进度由已读取InputSplit大小比总InputSplit大小
  93. }
  94. }
  95. public synchronized void close() throws IOException {
  96. if (in != null) {
  97. in.close();
  98. }
  99. }
  100. }

其它的一些RecordReader如SequenceFileRecordReader,CombineFileRecordReader.java等则对应不同的InputFormat。

下面继续看看这些RecordReader是如何被MapReduce框架使用的。

我们先看看Mapper.class是什么样的:

  1. public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
  2. public class Context
  3. extends MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
  4. public Context(Configuration conf, TaskAttemptID taskid,
  5. RecordReader<KEYIN,VALUEIN> reader,
  6. RecordWriter<KEYOUT,VALUEOUT> writer,
  7. OutputCommitter committer,
  8. StatusReporter reporter,
  9. InputSplit split) throws IOException, InterruptedException {
  10. super(conf, taskid, reader, writer, committer, reporter, split);
  11. }
  12. }
  13. /**
  14. * Called once at the beginning of the task.
  15. */
  16. protected void setup(Context context
  17. ) throws IOException, InterruptedException {
  18. // NOTHING
  19. }
  20. /**
  21. * Called once for each key/value pair in the input split. Most applications
  22. * should override this, but the default is the identity function.
  23. */
  24. @SuppressWarnings("unchecked")
  25. protected void map(KEYIN key, VALUEIN value,
  26. Context context) throws IOException, InterruptedException {
  27. context.write((KEYOUT) key, (VALUEOUT) value);
  28. }
  29. /**
  30. * Called once at the end of the task.
  31. */
  32. protected void cleanup(Context context
  33. ) throws IOException, InterruptedException {
  34. // NOTHING
  35. }
  36. /**
  37. * Expert users can override this method for more complete control over the
  38. * execution of the Mapper.
  39. * @param context
  40. * @throws IOException
  41. */
  42. public void run(Context context) throws IOException, InterruptedException {
  43. setup(context);
  44. while (context.nextKeyValue()) {
  45. map(context.getCurrentKey(), context.getCurrentValue(), context);
  46. }
  47. cleanup(context);
  48. }

我们写MapReduce程序的时候,我们写的mapper都要继承这个Mapper.class,通常我们会重写map()方法,map()每次接受一个K-V对,然后我们对这个K-V对进行处理,再分发出处理后的数据。我们也可能重写setup()以对这个map task进行一些预处理,比如创建一个List之类的;我们也可能重写cleanup()方法对做一些处理后的工作,当然我们也可能在cleanup()中写出K-V对。举个例子就是:InputSplit的数据是一些整数,然后我们要在mapper中算出它们的和。我们就可以在先设置个sum属性,然后map()函数处理一个K-V对就是将其加到sum上,最后在cleanup()函数中调用context.write(key,value);

最后我们看看Mapper.class中的run()方法,它相当于map task的驱动,我们可以看到run()方法首先调用setup()进行初始操作,然后对每个context.nextKeyValue()获取的K-V对,就调用map()函数进行处理,最后调用cleanup()做最后的处理。事实上,从text他.nextKeyValue()就是使用了相应的RecordReader来获取K-V对的。

我们看看Mapper.class中的Context类,它继承与MapContext,使用了一个RecordReader进行构造。下面我们再看这个MapContext

  1. public class MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT>
  2. extends TaskInputOutputContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
  3. private RecordReader<KEYIN,VALUEIN> reader;
  4. private InputSplit split;
  5. public MapContext(Configuration conf, TaskAttemptID taskid,
  6. RecordReader<KEYIN,VALUEIN> reader,
  7. RecordWriter<KEYOUT,VALUEOUT> writer,
  8. OutputCommitter committer,
  9. StatusReporter reporter,
  10. InputSplit split) {
  11. super(conf, taskid, writer, committer, reporter);
  12. this.reader = reader;
  13. this.split = split;
  14. }
  15. /**
  16. * Get the input split for this map.
  17. */
  18. public InputSplit getInputSplit() {
  19. return split;
  20. }
  21. @Override
  22. public KEYIN getCurrentKey() throws IOException, InterruptedException {
  23. return reader.getCurrentKey();
  24. }
  25. @Override
  26. public VALUEIN getCurrentValue() throws IOException, InterruptedException {
  27. return reader.getCurrentValue();
  28. }
  29. @Override
  30. public boolean nextKeyValue() throws IOException, InterruptedException {
  31. return reader.nextKeyValue();
  32. }
  33. }

我们可以看到MapContext直接是使用传入的RecordReader来进行K-V对的读取了。

到现在,我们已经知道输入文件是如何被读取、过滤、分片、读出K-V对,然后交给我们的Mapper类来处理的了。

最后,我们来看看FileInputFormat的几个子类。

TextInputFormat:

TextInputFormat是FileInputFormat的子类,其createRecordReader()方法返回的就是LineRecordReader。

  1. public class TextInputFormat extends FileInputFormat<LongWritable, Text> {
  2. @Override
  3. public RecordReader<LongWritable, Text>
  4. createRecordReader(InputSplit split,
  5. TaskAttemptContext context) {
  6. return new LineRecordReader();
  7. }
  8. @Override
  9. protected boolean isSplitable(JobContext context, Path file) {
  10. CompressionCodec codec =
  11. new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
  12. return codec == null;
  13. }
  14. }

我们还看到isSplitable()方法,当文件使用压缩的形式,这个文件就不可分割,否则就读取不到正确的数据了。这从某种程度上将影响分片的计算。有时我们希望一个文件只被一个Mapper处理的时候,我们就可以重写isSplitable()方法,告诉MapReduce框架,我哪些文件可以分割,哪些文件不能分割而只能作为一个分片。

NLineInputFormat;

  NLineInputFormat也是FileInputFormat的子类,与名字一致,它是根据行数来划分InputSplits而不是像TextInputFormat那样依赖分片大小和行的长度的。也就是说,TextInputFormat当一行很长或分片比较小时,获取的分片可能只包含很少的K-V对,这样一个map task处理的K-V对就很少,这可能很不理想。因此我们可以使用NLineInputFormat来控制一个map task处理的K-V对,这是通过分割InputSplits时按行数分割的方法来实现的,这我们在代码中可以看出来。我们可以设置mapreduce.input.lineinputformat.linespermap来设置这个行数。
  1. public class NLineInputFormat extends FileInputFormat<LongWritable, Text> {
  2. public static final String LINES_PER_MAP =
  3. "mapreduce.input.lineinputformat.linespermap";
  4. public RecordReader<LongWritable, Text> createRecordReader(
  5. InputSplit genericSplit, TaskAttemptContext context)
  6. throws IOException {
  7. context.setStatus(genericSplit.toString());
  8. return new LineRecordReader();
  9. }
  10. /**
  11. * Logically splits the set of input files for the job, splits N lines
  12. * of the input as one split.
  13. *
  14. * @see FileInputFormat#getSplits(JobContext)
  15. */
  16. public List<InputSplit> getSplits(JobContext job)
  17. throws IOException {
  18. List<InputSplit> splits = new ArrayList<InputSplit>();
  19. int numLinesPerSplit = getNumLinesPerSplit(job);
  20. for (FileStatus status : listStatus(job)) {
  21. splits.addAll(getSplitsForFile(status,
  22. job.getConfiguration(), numLinesPerSplit));
  23. }
  24. return splits;
  25. }
  26. public static List<FileSplit> getSplitsForFile(FileStatus status,
  27. Configuration conf, int numLinesPerSplit) throws IOException {
  28. List<FileSplit> splits = new ArrayList<FileSplit> ();
  29. Path fileName = status.getPath();
  30. if (status.isDir()) {
  31. throw new IOException("Not a file: " + fileName);
  32. }
  33. FileSystem  fs = fileName.getFileSystem(conf);
  34. LineReader lr = null;
  35. try {
  36. FSDataInputStream in  = fs.open(fileName);
  37. lr = new LineReader(in, conf);
  38. Text line = new Text();
  39. int numLines = 0;
  40. long begin = 0;
  41. long length = 0;
  42. int num = -1;
  43. while ((num = lr.readLine(line)) > 0) {
  44. numLines++;
  45. length += num;
  46. if (numLines == numLinesPerSplit) {
  47. // NLineInputFormat uses LineRecordReader, which always reads
  48. // (and consumes) at least one character out of its upper split
  49. // boundary. So to make sure that each mapper gets N lines, we
  50. // move back the upper split limits of each split
  51. // by one character here.
  52. if (begin == 0) {
  53. splits.add(new FileSplit(fileName, begin, length - 1,
  54. new String[] {}));
  55. } else {
  56. splits.add(new FileSplit(fileName, begin - 1, length,
  57. new String[] {}));
  58. }
  59. begin += length;
  60. length = 0;
  61. numLines = 0;
  62. }
  63. }
  64. if (numLines != 0) {
  65. splits.add(new FileSplit(fileName, begin, length, new String[]{}));
  66. }
  67. } finally {
  68. if (lr != null) {
  69. lr.close();
  70. }
  71. }
  72. return splits;
  73. }
  74. /**
  75. * Set the number of lines per split
  76. * @param job the job to modify
  77. * @param numLines the number of lines per split
  78. */
  79. public static void setNumLinesPerSplit(Job job, int numLines) {
  80. job.getConfiguration().setInt(LINES_PER_MAP, numLines);
  81. }
  82. /**
  83. * Get the number of lines per split
  84. * @param job the job
  85. * @return the number of lines per split
  86. */
  87. public static int getNumLinesPerSplit(JobContext job) {
  88. return job.getConfiguration().getInt(LINES_PER_MAP, 1);
  89. }

现在,我们对Hadoop的输入格式和其在MapReduce中如何被使用有了具体的了解了。

 

(一)MapReduce篇之InputFormat,InputSplit,RecordReader(转)的更多相关文章

  1. [Hadoop源码解读](一)MapReduce篇之InputFormat

    平时我们写MapReduce程序的时候,在设置输入格式的时候,总会调用形如job.setInputFormatClass(KeyValueTextInputFormat.class);来保证输入文件按 ...

  2. [Hadoop源码解读](六)MapReduce篇之MapTask类

    MapTask类继承于Task类,它最主要的方法就是run(),用来执行这个Map任务. run()首先设置一个TaskReporter并启动,然后调用JobConf的getUseNewAPI()判断 ...

  3. [Hadoop源码解读](四)MapReduce篇之Counter相关类

    当我们定义一个Counter时,我们首先要定义一枚举类型: public static enum MY_COUNTER{ CORRUPTED_DATA_COUNTER, NORMAL_DATA_COU ...

  4. [Hadoop源码解读](二)MapReduce篇之Mapper类

    前面在讲InputFormat的时候,讲到了Mapper类是如何利用RecordReader来读取InputSplit中的K-V对的. 这一篇里,开始对Mapper.class的子类进行解读. 先回忆 ...

  5. MapReduce之自定义InputFormat

    在企业开发中,Hadoop框架自带的InputFormat类型不能满足所有应用场景,需要自定义InputFormat来解决实际问题. 自定义InputFormat步骤如下: (1)自定义一个类继承Fi ...

  6. [Hadoop源码解读](三)MapReduce篇之Job类

    下面,我们只涉及MapReduce 1,而不涉及YARN. 当我们在写MapReduce程序的时候,通常,在main函数里,我们会像下面这样做.建立一个Job对象,设置它的JobName,然后配置输入 ...

  7. Hadoop2源码分析-MapReduce篇

    1.概述 前面我们已经对Hadoop有了一个初步认识,接下来我们开始学习Hadoop的一些核心的功能,其中包含mapreduce,fs,hdfs,ipc,io,yarn,今天为大家分享的是mapred ...

  8. [Hadoop源码解读](五)MapReduce篇之Writable相关类

    前面讲了InputFormat,就顺便讲一下Writable的东西吧,本来应当是放在HDFS中的. 当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节 ...

  9. MapReduce自定义InputFormat,RecordReader

    MapReduce默认的InputFormat是TextInputFormat,且key是偏移量,value是文本,自定义InputFormat需要实现FileInputFormat,并重写creat ...

随机推荐

  1. easyui Tooltip 气泡信息提示

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  2. 实现 UISegmentControl 与 UIScrollView的上下级联(分别在相应的方法中加入级联代码)

    实现 UISegmentControl 与 UIScrollView的上下级联,需要在 [segmentCtr addTarget:self action:@selector(segmentedCon ...

  3. Swift-06-闭包

    看完记不住,只好继续抄课文. 如果某个存储型属性的默认值需要特别的定制或者准备,就可以使用闭包或者全局函数来为其属性提供定制的默认值.每当某个属性所属的新类型实例创建时,对应的闭包或者函数会被调用,而 ...

  4. div滑入与滑出

    html <div class="pop_tit"> <span class="p_tit1" title="大连未来城LECITY ...

  5. How to read the HTML DTD

    Contents How to read the HTML DTD 1. DTD Comments 2. Parameter Entity definitions 3. Element declara ...

  6. 不连数据库List分页

    package com.jpsycn.kfwggl.common.crawl; import java.util.ArrayList; import java.util.List; public cl ...

  7. mysql -B 恢复与不加

    -B 跟--database 意义一样 在默认不指定库时候 连续名称,只有第一个名称为库名,后面的都为表名 而使用 -B 或者 --database 之后 所有的名 都是库名 1 导出单个库时候加了- ...

  8. [BS-13] 创建和注册UITableViewCell及Storyboard和Xib区别

    创建和注册UITableViewCell及Storyboard和Xib区别 // 界面创建完成被调用 - (void)viewDidLoad { [super viewDidLoad]; /** 如果 ...

  9. 第四篇 Integration Services:增量加载-Updating Rows

    本篇文章是Integration Services系列的第四篇,详细内容请参考原文. 回顾增量加载记住,在SSIS增量加载有三个使用案例:1.New rows-add rows to the dest ...

  10. 手机大数据_SQL映射对象_动软_代码模板_Models

    <#@ template language="c#" HostSpecific="True" #> <#@ output extension= ...