hadoop会对原始输入文件进行文件切割,然后把每个split传入mapper程序中进行处理,FileInputFormat是所有以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的所有文件,并实现了对输入文件计算splits的方法。至于获得记录的方法是有不同的子类进行实现的。

那么,FileInputFormat是怎样将他们划分成splits的呢?FileInputFormat只划分比HDFS block大的文件,所以如果一个文件的大小比block小,将不会被划分,这也是Hadoop处理大文件的效率要比处理很多小文件的效率高的原因。

hadoop默认的InputFormat是TextInputFormat,重写了FileInputFormat中的createRecordReader和isSplitable方法。该类使用的reader是LineRecordReader,即以回车键(CR = 13)或换行符(LF = 10)为行分隔符。

但大多数情况下,回车键或换行符作为输入文件的行分隔符并不能满足我们的需求,通常用户很有可能会输入回车键、换行符,所以通常我们会定义不可见字符(即用户无法输入的字符)为行分隔符,这种情况下,就需要新写一个InputFormat。

又或者,一条记录的分隔符不是字符,而是字符串,这种情况相对麻烦;还有一种情况,输入文件的主键key已经是排好序的了,需要hadoop做的只是把相同的key作为一个数据块进行逻辑处理,这种情况更麻烦,相当于免去了mapper的过程,直接进去reduce,那么InputFormat的逻辑就相对较为复杂了,但并不是不能实现。

1、改变一条记录的分隔符,不用默认的回车或换行符作为记录分隔符,甚至可以采用字符串作为记录分隔符
     1)自定义一个InputFormat,继承FileInputFormat,重写createRecordReader方法,如果不需要分片或者需要改变分片的方式,则重写isSplitable方法,具体代码如下:

public class FileInputFormatB extends FileInputFormat<LongWritable, Text> {

   @Override

   public RecordReader<LongWritable, Text> createRecordReader( InputSplit split, TaskAttemptContext context) {
        return new SearchRecordReader("\b");

    }

    @Override
    protected boolean isSplitable(FileSystem fs, Path filename) {
         // 输入文件不分片
        return false;
     }
}

2)关键在于定义一个新的SearchRecordReader继承RecordReader,支持自定义的行分隔符,即一条记录的分隔符。标红的地方为与hadoop默认的LineRecordReader不同的地方。

 public class IsearchRecordReader extends RecordReader<LongWritable, Text> {
  private static final Log LOG = LogFactory.getLog(IsearchRecordReader.class);

  private CompressionCodecFactory compressionCodecs = null;
  private long start;
  private long pos;
  private long end;
  private LineReader in;
  private int maxLineLength;
  private LongWritable key = null;
  private Text value = null;
  //行分隔符,即一条记录的分隔符
  private byte[] separator = {'\b'};
  private int sepLength = 1;

 ‍ public IsearchRecordReader(){
  }
  public IsearchRecordReader(String seps){
   this.separator = seps.getBytes();
   sepLength = separator.length;
  }

  public void initialize(InputSplit genericSplit, TaskAttemptContext context) throws IOException {
   FileSplit split = (FileSplit) genericSplit;
   Configuration job = context.getConfiguration();
   this.maxLineLength = job.getInt("mapred.linerecordreader.maxlength", Integer.MAX_VALUE);

   this.start = split.getStart();
   this.end = (this.start + split.getLength());
   Path file = split.getPath();
   this.compressionCodecs = new CompressionCodecFactory(job);
   CompressionCodec codec = this.compressionCodecs.getCodec(file);

   // open the file and seek to the start of the split
   FileSystem fs = file.getFileSystem(job);
   FSDataInputStream fileIn = fs.open(split.getPath());
   boolean skipFirstLine = false;
   if (codec != null) {
    this.in = new LineReader(codec.createInputStream(fileIn), job);
    this.end = Long.MAX_VALUE;
   } else {
    if (this.start != 0L) {
     skipFirstLine = true;
     this.start -= sepLength;
     fileIn.seek(this.start);
    }
    this.in = new LineReader(fileIn, job);
   }
   if (skipFirstLine) { // skip first line and re-establish "start".
    int newSize = in.readLine(new Text(), 0, (int) Math.min( (long) Integer.MAX_VALUE, end - start));

    if(newSize > 0){
     start += newSize;
    }
   }

   this.pos = this.start;
  }

  public boolean nextKeyValue() throws IOException {
   if (this.key == null) {
    this.key = new LongWritable();
   }
   this.key.set(this.pos);
   if (this.value == null) {
    this.value = new Text();
   }
   int newSize = 0;
   while (this.pos < this.end) {
    newSize = this.in.readLine(this.value, this.maxLineLength, Math.max(
  (int) Math.min(Integer.MAX_VALUE, this.end - this.pos), this.maxLineLength));

    if (newSize == 0) {
     break;
    }
    this.pos += newSize;
    if (newSize < this.maxLineLength) {
     break;
    }

    LOG.info("Skipped line of size " + newSize + " at pos " + (this.pos - newSize));
   }

   if (newSize == 0) {
    //读下一个buffer
    this.key = null;
    this.value = null;
    return false;
   }
   //读同一个buffer的下一个记录
   return true;
  }

  public LongWritable getCurrentKey() {
   return this.key;
  }

  public Text getCurrentValue() {
   return this.value;
  }

  public float getProgress() {
   if (this.start == this.end) {
    return 0.0F;
   }
   return Math.min(1.0F, (float) (this.pos - this.start) / (float) (this.end - this.start));
  }

  public synchronized void close() throws IOException {
   if (this.in != null)
    this.in.close();
  }

 }

3)重写SearchRecordReader需要的LineReader,可作为SearchRecordReader内部类。特别需要注意的地方就是,读取文件的方式是按指定大小的buffer来读,必定就会遇到一条完整的记录被切成两半,甚至如果分隔符大于1个字符时分隔符也会被切成两半的情况,这种情况一定要加以拼接处理。

 public class LineReader {
   //回车键(hadoop默认)
   //private static final byte CR = 13;
   //换行符(hadoop默认)
   //private static final byte LF = 10;

   //按buffer进行文件读取
   private static final int DEFAULT_BUFFER_SIZE = 32 * 1024 * 1024;
   private int bufferSize = DEFAULT_BUFFER_SIZE;
   private InputStream in;
   private byte[] buffer;
   private int bufferLength = 0;
   private int bufferPosn = 0;

   LineReader(InputStream in, int bufferSize) {
    this.bufferLength = 0;
     this.bufferPosn = 0;

    this.in = in;
    this.bufferSize = bufferSize;
    this.buffer = new byte[this.bufferSize];
   }

   public LineReader(InputStream in, Configuration conf) throws IOException {
    this(in, conf.getInt("io.file.buffer.size", DEFAULT_BUFFER_SIZE));
   }

   public void close() throws IOException {
    in.close();
   }

  public int readLine(Text str, int maxLineLength) throws IOException {
    return readLine(str, maxLineLength, Integer.MAX_VALUE);
   }

   public int readLine(Text str) throws IOException {
    return readLine(str, Integer.MAX_VALUE, Integer.MAX_VALUE);
   }

   //以下是需要改写的部分_start,核心代码

   public int readLine(Text str, int maxLineLength, int maxBytesToConsume) throws IOException{
    str.clear();
    Text record = new Text();
    int txtLength = 0;
    long bytesConsumed = 0L;
    boolean newline = false;
    int sepPosn = 0;

    do {
     //已经读到buffer的末尾了,读下一个buffer
     if (this.bufferPosn >= this.bufferLength) {
      bufferPosn = 0;
      bufferLength = in.read(buffer);

      //读到文件末尾了,则跳出,进行下一个文件的读取
      if (bufferLength <= 0) {
       break;
      }
     }

     int startPosn = this.bufferPosn;
     for (; bufferPosn < bufferLength; bufferPosn ++) {
      //处理上一个buffer的尾巴被切成了两半的分隔符(如果分隔符中重复字符过多在这里会有问题)
      if(sepPosn > 0 && buffer[bufferPosn] != separator[sepPosn]){
       sepPosn = 0;
      }

      //遇到行分隔符的第一个字符
      if (buffer[bufferPosn] == separator[sepPosn]) {
       bufferPosn ++;
       int i = 0;

       //判断接下来的字符是否也是行分隔符中的字符
       for(++ sepPosn; sepPosn < sepLength; i ++, sepPosn ++){

        //buffer的最后刚好是分隔符,且分隔符被不幸地切成了两半
        if(bufferPosn + i >= bufferLength){
         bufferPosn += i - 1;
         break;
        }

        //一旦其中有一个字符不相同,就判定为不是分隔符
        if(this.buffer[this.bufferPosn + i] != separator[sepPosn]){
         sepPosn = 0;
         break;
        }
       }

       //的确遇到了行分隔符
       if(sepPosn == sepLength){
        bufferPosn += i;
        newline = true;
        sepPosn = 0;
        break;
       }
      }
     }

     int readLength = this.bufferPosn - startPosn;

     bytesConsumed += readLength;
     //行分隔符不放入块中
     //int appendLength = readLength - newlineLength;
     if (readLength > maxLineLength - txtLength) {
      readLength = maxLineLength - txtLength;
     }
     if (readLength > 0) {
      record.append(this.buffer, startPosn, readLength);
      txtLength += readLength;

      //去掉记录的分隔符
      if(newline){
       str.set(record.getBytes(), 0, record.getLength() - sepLength);
      }
     }

    } while (!newline && (bytesConsumed < maxBytesToConsume));

    if (bytesConsumed > (long)Integer.MAX_VALUE) {
     throw new IOException("Too many bytes before newline: " + bytesConsumed);
    }

    return (int) bytesConsumed;
   }

   //以下是需要改写的部分_end

 //以下是hadoop-core中LineReader的源码_start

 public int readLine(Text str, int maxLineLength, int maxBytesToConsume) throws IOException{
     str.clear();
     int txtLength = 0;
     int newlineLength = 0;
     boolean prevCharCR = false;
     long bytesConsumed = 0L;
     do {
       int startPosn = this.bufferPosn;
       if (this.bufferPosn >= this.bufferLength) {
         startPosn = this.bufferPosn = 0;
         if (prevCharCR)  bytesConsumed ++;
         this.bufferLength = this.in.read(this.buffer);
         if (this.bufferLength <= 0)  break;
       }
       for (; this.bufferPosn < this.bufferLength; this.bufferPosn ++) {
         if (this.buffer[this.bufferPosn] == LF) {
           newlineLength = (prevCharCR) ? 2 : 1;
           this.bufferPosn ++;
           break;
         }
         if (prevCharCR) {
           newlineLength = 1;
           break;
         }
         prevCharCR = this.buffer[this.bufferPosn] == CR;
       }
       int readLength = this.bufferPosn - startPosn;
       if ((prevCharCR) && (newlineLength == 0))
         --readLength;
       bytesConsumed += readLength;
       int appendLength = readLength - newlineLength;
       if (appendLength > maxLineLength - txtLength) {
         appendLength = maxLineLength - txtLength;
       }
       if (appendLength > 0) {
         str.append(this.buffer, startPosn, appendLength);
         txtLength += appendLength; }
     }
     while ((newlineLength == 0) && (bytesConsumed < maxBytesToConsume));

     if (bytesConsumed > (long)Integer.MAX_VALUE) throw new IOException("Too many bytes before newline: " + bytesConsumed);
     return (int)bytesConsumed;
   }

 //以下是hadoop-core中LineReader的源码_end

 }

2、已经按主键key排好序了,并保证相同主键key一定是在一起的,假设每条记录的第一个字段为主键,那么如果沿用上面的LineReader,需要在核心方法readLine中对前后两条记录的id进行equals判断,如果不同才进行split,如果相同继续下一条记录的判断。代码就不再贴了,但需要注意的地方,依旧是前后两个buffer进行交接的时候,非常有可能一条记录被切成了两半,一半在前一个buffer中,一半在后一个buffer中。

这种方式的好处在于少去了reduce操作,会大大地提高效率,其实mapper的过程相当的快,费时的通常是reduce。

hadoop2.2编程:自定义hadoop map/reduce输入文件切割InputFormat的更多相关文章

  1. Hadoop Map/Reduce教程

    原文地址:http://hadoop.apache.org/docs/r1.0.4/cn/mapred_tutorial.html 目的 先决条件 概述 输入与输出 例子:WordCount v1.0 ...

  2. 一步一步跟我学习hadoop(5)----hadoop Map/Reduce教程(2)

    Map/Reduce用户界面 本节为用户採用框架要面对的各个环节提供了具体的描写叙述,旨在与帮助用户对实现.配置和调优进行具体的设置.然而,开发时候还是要相应着API进行相关操作. 首先我们须要了解M ...

  3. Hadoop Map/Reduce

    Hadoop Map/Reduce是一个使用简易的软件框架,基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理上T级别的数据集.一个Map/Reduce ...

  4. Hadoop Map/Reduce 示例程序WordCount

    #进入hadoop安装目录 cd /usr/local/hadoop #创建示例文件:input #在里面输入以下内容: #Hello world, Bye world! vim input #在hd ...

  5. Hadoop Map/Reduce的工作流

    问题描述 我们的数据分析平台是单一的Map/Reduce过程,由于半年来不断地增加需求,导致了问题已经不是那么地简单,特别是在Reduce阶段,一些大对象会常驻内存.因此越来越顶不住压力了,当前内存问 ...

  6. hadoop2.2编程:hadoop性能测试

    <hadoop the definitive way>(third version)中的Benchmarking a Hadoop Cluster Test Cases 的class在新的 ...

  7. Python学习:函数式编程(lambda, map() ,reduce() ,filter())

    1. lambda: Python 支持用lambda对简单的功能定义“行内函数” 2.map() : 3.reduce() : 4.filter() : map() ,reduce() , filt ...

  8. (转载)Hadoop map reduce 过程获取环境变量

    来源:http://www.linuxidc.com/Linux/2012-07/66337.htm   作者: lmc_wy Hadoop任务执行过程中,在每一个map节点或者reduce节点能获取 ...

  9. Hadoop map reduce 任务数量优化

    mapred.tasktracker.map.tasks.maximum 官方解释:The maximum number of map tasks that will be run  simultan ...

随机推荐

  1. Harry Potter

    Names appearing in "Harry Potter" 1.Harry Potter ①Harry is from Henry. ②Harry is related t ...

  2. C# 调用load事件

    在一个函数或者事件中调用另外的事件,例如调用Load事件 private void EventForm_Load(object sender, EventArgs e) { //相关内容 } priv ...

  3. Makefile当中宏定义传递字符串

    前几天遇到类似的问题[http://bbs.chinaunix.net/thread-1589386-1-1.html]: 在Makefile里面定义一个字符串在程序里面使用, CFLAGS += - ...

  4. PHPstrom 增加emmet插件

    之前记得使用Eclipse的时候有一个插件叫 emmet 可以实现快速开发前端,简直就是前端开发秒杀神器: 输入对应的代码一个table键就搞定了一堆代码: 在emmet的官网上 看到其实是支持的PH ...

  5. poj 2533 Longest Ordered Subsequence 最长递增子序列

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4098562.html 题目链接:poj 2533 Longest Ordered Subse ...

  6. gb2312编码提交url乱码解决

    gb2312编码提交url,服务器接收时出现乱码,用System.Web.HttpUtility.UrlDecode();解码 ,还是出现乱码,困老了我好长时间,终于在google上找到了解决办法. ...

  7. DZ升级到X3.2后,UCenter用户管理中心进不了了

    前天将DZ升级到X3.2后,UCenter用户管理中心进不了了,输入的密码也对,验证码也对,就是点登录后没反应,又回来输入前的状态.如果更换密码后,显示密码错误,证明密码是没错的.但就是进不了.大家看 ...

  8. MySQL基础学习之索引

    创建新表新索引 CREATE TABLE 表名(数据名 类型,INDEX  索引名称(属性)) 创建存在表的索引 CREATE INDEX 索引名称  ON 表名(属性) 修改索引 ALTER TAB ...

  9. CRUD生成器DBuilder设计与实现

    源码位于github:https://github.com/lvyahui8/dbuilder.git .文中图片如果太小看不清楚,请右键点击“在新标签页中打开”即可看到原图 有兴趣还可以加QQ群交流 ...

  10. ECSHOP模板设置,前台英文后台中文,无需复制

    很多做英文站的朋友 只想让前台显示为英文,后台依就保持中文.这个要如何来做呢?网上也看到类似文章,好像还要进行目录复制与覆盖.我下面这个方法更简单,无需复制. 第一步: 通过后台设置实现前台英文.进入 ...