MR的inputFormat总结

inputFormat

hadoop虽然内置了很多个inputFormat但是,有时候还是不满足我们的一些需求,所以我们需要重写一个类,来实现我们对数据的读取。

例如WC我们制定几行一起读取:

所有InputFormat都要直接或间接的继承InputFormat抽象类。
		InputFormat接 口中主要定义了如下两个方法:
			/**
				生产InputSplit集合的方法
				此方法接受JobContext接受环境信息,得到要处理的文件信息后,进行逻辑切割,产生InputSplit集合返回
			*/
			List getSplits(JobContext context) throws IOException, InterruptedException;
			/**
				此方法返回RecordReader对象。
				一个RecordReader包含方法描述如何从InputSplit 切分出要送入 Mapper 的K1 V1对
			*/
			public abstract RecordReader createRecordReader(InputSplit split,TaskAttemptContext context) throws IOException, InterruptedException;
		

我们可以直接继承InputFormat,但更多的时候我们会选择继承他的一个实现子类,比如FileInputFormat -- 此类是所有来源为文件的InputFormat的基类,默认的TextInputFormat就继承自它。
FileInputFormat实现了InputFormat接口,实现了getSplits方法,根据配置去逻辑切割文件,返回FileSplit的集合,并提供了isSplitable()方法,子类可以通过在这个方法中返回boolean类型的值表明是否要对文件进行逻辑切割,如果返回false则无论文件是否超过一个Block大小都不会进行切割,而将这个文件作为一个逻辑块返回。而对createRecordReader方法则没有提供实现,设置为了抽象方法,要求子类实现。 
如果想要更精细的改变逻辑切块规则可以覆盖getSplits方法自己编写代码实现。 而更多的时候,我们直接使用父类中的方法而将精力放置在createRecordReader上,决定如何将InputSplit转换为一个个的Recoder

inputFormat hadoop的两个jar包都提供了,org.apache.hadoop.mapred是老版本的API,是一个接口,mapreduce是抽象类,它们互相之间不兼容,所以我们用新的。

但是我们要是读取文件的话,可以直接使用fileinputformat 它里面也是重写了getSplits方法,但是他没有另一个RecordReader方法,那就需要我们使用TextInputFormat FileInputFomat

public List getSplits(JobContext job) throws IOException {
    //首先是开启了一个监听
    StopWatch sw = new StopWatch().start();
    获取切片的大小,(默认的大小1,从我们的job设置的切片最小值)取最大值作为最小值
    long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
    long maxSize = getMaxSplitSize(job);

    // generate splits
    List splits = new ArrayList();
    //后去当前job代表的所有的文件类。它实现类Writable, Comparable两个类
    List files = listStatus(job);
    //遍历所有文件
    for (FileStatus file: files) {
      Path path = file.getPath();
      long length = file.getLen();
      //如果文件有内容
      if (length != 0) {
        BlockLocation[] blkLocations;
        if (file instanceof LocatedFileStatus) {
        //如果是本地文件,直接获取文件块
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
        
          FileSystem fs = path.getFileSystem(job.getConfiguration());
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
        //下面的这个方法默认返回了true,我们可以自定义这个方法覆盖他,然后就可以控制文件是否切割,如果返回false,那么文件就不会进行切割了。
        if (isSplitable(job, path)) {
          long blockSize = file.getBlockSize();
          long splitSize = computeSplitSize(blockSize, minSize, maxSize);

          long bytesRemaining = length;
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                        blkLocations[blkIndex].getHosts(),
                        blkLocations[blkIndex].getCachedHosts()));
            bytesRemaining -= splitSize;
          }

          if (bytesRemaining != 0) {
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
                       blkLocations[blkIndex].getHosts(),
                       blkLocations[blkIndex].getCachedHosts()));
          }
        } else { // not splitable
          splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
                      blkLocations[0].getCachedHosts()));
        }
      } else { 
        //Create empty hosts array for zero length files
        splits.add(makeSplit(path, 0, length, new String[0]));
      }
    }
    // Save the number of input files for metrics/loadgen
    job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
    sw.stop();
    if (LOG.isDebugEnabled()) {
      LOG.debug("Total # of splits generated by getSplits: " + splits.size()
          + ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
    }
    return splits;
  }

TextInputFormat 它又实现了fileInputFormat没有实现的RecordReader方法,重写了父类需要我们覆盖的isSplitable方法。

@InterfaceAudience.Public
@InterfaceStability.Stable
public class TextInputFormat extends FileInputFormat
  implements JobConfigurable {

  private CompressionCodecFactory compressionCodecs = null;
  
  public void configure(JobConf conf) {
    compressionCodecs = new CompressionCodecFactory(conf);
  }
  
  protected boolean isSplitable(FileSystem fs, Path file) {
    final CompressionCodec codec = compressionCodecs.getCodec(file);
    if (null == codec) {
      return true;
    }
    return codec instanceof SplittableCompressionCodec;
  }

  public RecordReader getRecordReader(
                                          InputSplit genericSplit, JobConf job,
                                          Reporter reporter)
    throws IOException {
    
    reporter.setStatus(genericSplit.toString());
    String delimiter = job.get("textinputformat.record.delimiter");
    byte[] recordDelimiterBytes = null;
    if (null != delimiter) {
      recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
    }
    //下面这个方法中封装了我们需要的行读取器,然后读取,
    return new LineRecordReader(job, (FileSplit) genericSplit,
        recordDelimiterBytes);
  }
}

重写MyRecordReader

public class MyRecordReader extends RecordReader {
    //
    private FileSplit split=null;
    private Path path=null;
    private LineReader lineReader=null;
    private Text key=null;
    private Text value=null;

    private boolean  haseNo=true;

    @Override
    public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
        this.split=(FileSplit) split;
        this.path=((FileSplit) split).getPath();
        Configuration conf=context.getConfiguration();
        FileSystem fileSystem = path.getFileSystem(conf);

        FSDataInputStream in = fileSystem.open(path);
        lineReader = new LineReader(in);

    }
    //每次调用这个方法得到下一个key和value的值,但是我们目前是想要四行作为一个key和value,所以,
    //需要自此你故意返回的keyvalue,但是这个input在init方法中,所以需要定一个全局变量来得到。
    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        //读取的行数
        key=new Text();
        value=new Text();
        //处理可能出现文件不全的情况
        //定义一个计数器,记录本次读取到了多少行
        int count=0;
        //定义了一个temp的临时记录内容
        Text temp=new Text();
        for(int i=0;i<4;i++){

            int num = lineReader.readLine(temp);
            //没有读取到数据
            if(num==0){
                //不能直接返回false,因为有可能其他行读取到了数据
                haseNo=false;
                break;
            }else{
                value.append(temp.getBytes(),0,temp.getLength());
                //要是几行合并读取,需要人为的给每行读完加一个换行
                value.append("\r\n".getBytes(),0,"\r\n".length());
                count++;
            }
            //表示没有读取导数据
            if(count==0){
                return false;
            }else {
                key.set(count+"");
                return true;
            }

        }


        return false;
    }

    @Override
    public Text getCurrentKey() throws IOException, InterruptedException {
        return key;
    }

    @Override
    public Text getCurrentValue() throws IOException, InterruptedException {
        return value;
    }

    @Override
    public float getProgress() throws IOException, InterruptedException {
        return 0;
    }

    @Override
    public void close() throws IOException {
        if(lineReader!=null){
            lineReader.close();
        }
    }
}

你可能感兴趣的:(hadoop,hadoop,mr)