我们知道,任何一个工程项目,最重要的是三个部分:输入,中间处理,输出。今天我们来深入的了解一下我们熟知的Hadoop系统中,输入是如何输入的?

  在hadoop中,输入数据都是通过对应的InputFormat类和RecordReader类来实现的,其中InputFormat来实现将对应输入文件进行分片,RecordReader类将对应分片中的数据读取进来。具体的方式如下:

(1)InputFormat类是一个接口。

public interface InputFormat<K, V> {

 

  InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;

 

  RecordReader<K, V> getRecordReader(InputSplit split,

                                     JobConf job,

                                     Reporter reporter) throws IOException;

}

(2)FileInputFormat类实现了InputFormat接口。该类实现了getSplits方法,但是它也没有实现对应的getRecordReader方法。也就是说FileInputFormat还是一个抽象类。这里需要说明的一个问题是,FileInputFormat用isSplitable方法来指定对应的文件是否支持数据的切分。默认情况下都是支持的,一般子类都需要重新实现它。

public abstract class FileInputFormat<K, V> implements InputFormat<K, V> {

public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException {

FileStatus[] files = listStatus(job);

// Save the number of input files in the job-conf

job.setLong(NUM_INPUT_FILES, files.length);

long totalSize = 0;                           // compute total size

for (FileStatus file: files) {                // check we have valid files

if (file.isDir()) {

throw new IOException("Not a file: "+ file.getPath());

}

totalSize += file.getLen();

}

long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);

long minSize = Math.max(job.getLong("mapred.min.split.size", 1),

minSplitSize);

// generate splits

ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);

NetworkTopology clusterMap = new NetworkTopology();

for (FileStatus file: files) {

Path path = file.getPath();

FileSystem fs = path.getFileSystem(job);

long length = file.getLen();

BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);

if ((length != 0) && isSplitable(fs, path)) {

long blockSize = file.getBlockSize();

long splitSize = computeSplitSize(goalSize, minSize, blockSize);

long bytesRemaining = length;

while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {

String[] splitHosts = getSplitHosts(blkLocations,

length-bytesRemaining, splitSize, clusterMap);

splits.add(new FileSplit(path, length-bytesRemaining, splitSize,

splitHosts));

bytesRemaining -= splitSize;

}

if (bytesRemaining != 0) {

splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,

blkLocations[blkLocations.length-1].getHosts()));

}

} else if (length != 0) {

String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);

splits.add(new FileSplit(path, 0, length, splitHosts));

} else {

//Create empty hosts array for zero length files

splits.add(new FileSplit(path, 0, length, new String[0]));

}

}

LOG.debug("Total # of splits: " + splits.size());

return splits.toArray(new FileSplit[splits.size()]);

}

//该方法是用来判断是否可以进行数据的切分

  protected boolean isSplitable(FileSystem fs, Path filename) {

    return true;

  }

//但是它也没有实现对应的getRecordReader方法。也就是说FileInputFormat还是一个抽象类。

public abstract RecordReader<K, V> getRecordReader(InputSplit split,

JobConf job,

Reporter reporter)  throws IOException;

(3)TextFileInputFormat类仅仅实现了FileInputFormat类的getRecordReader方法,并且重写了isSplitable方法,他并没有实现getSplits方法,由此可知,他的getSplits的实现还是交由父类FileInputFormat来实现的。(这里需要注意TextFileInputFormat并不是InputFormat的子类,TextFileInputFormat它仅仅是继承了InputFormat的getRecordReader的方法而已。)

public class TextInputFormat extends FileInputFormat<LongWritable, Text>

implements JobConfigurable {

private CompressionCodecFactory compressionCodecs = null;

public void configure(JobConf conf) {

compressionCodecs = new CompressionCodecFactory(conf);

}

  //子类重新实现了isSplitable方法

protected boolean isSplitable(FileSystem fs, Path file) {

final CompressionCodec codec = compressionCodecs.getCodec(file);

if (null == codec) {

return true;

}

return codec instanceof SplittableCompressionCodec;

}

//该方法实现了将文件中的数据读入到对应的Map方法中。

  public RecordReader<LongWritable, Text> 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();

    return new LineRecordReader(job, (FileSplit) genericSplit,

        recordDelimiterBytes);

  }

}

从上面可以看出一个Text格式的文件是通过什么样的类继承层次输入到map方法中。下面主要介绍一下,到底是如何切分的?我们从类的继承层次关系上可以看出,具体的切分方式是通过FileInputFormat类来实现的。因此,要了解文件是如何切分的,只需要查看一下FileInputFormat类中的getSplits方法的实现细节即可。下面我再次把FileInputFormat类中的getSplits方法贴出来:然后分析每一句代码。

public InputSplit[] getSplits(JobConf job, int numSplits)

throws IOException {

FileStatus[] files = listStatus(job); //列出当前job中所有的输入文件

// Save the number of input files in the job-conf

job.setLong(NUM_INPUT_FILES, files.length); //设置当前job的输入文件数目

//计算当前job所有输入文件总的大小

long totalSize = 0;                           // compute total size

//遍历每一个文件

for (FileStatus file: files) {               // check we have valid files

if (file.isDir()) {

throw new IOException("Not a file: "+ file.getPath());

}

totalSize += file.getLen();

}

// numSplits是分片数,goalSize是平均每一个分片的大小,minSize是每个分片最小值

long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);

long minSize = Math.max(job.getLong("mapred.min.split.size", 1),

minSplitSize);

// generate splits  计算分片

ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);

NetworkTopology clusterMap = new NetworkTopology();

for (FileStatus file: files) {

Path path = file.getPath();

FileSystem fs = path.getFileSystem(job);

long length = file.getLen();

//获取文件的位置

BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);

  //isSplitable方法根据对应文件名称判断对应文件是否可以切分

if ((length != 0) && isSplitable(fs, path)) {

long blockSize = file.getBlockSize();//获取文件块的大小

// computeSplitSize方法计算真正的分片大小

long splitSize = computeSplitSize(goalSize, minSize, blockSize);

long bytesRemaining = length;//文件剩余大小

    // SPLIT_SLOP=1.1,文件大小/分片的大小> SPLIT_SLOP则进行切分。

while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {

  // splitHosts用来记录分片元数据信息(包括切片的位置,大小等等)

String[] splitHosts = getSplitHosts(blkLocations,

length-bytesRemaining, splitSize, clusterMap);

splits.add(new FileSplit(path, length-bytesRemaining, splitSize,

splitHosts));

bytesRemaining -= splitSize;

}

if (bytesRemaining != 0) {

splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,

blkLocations[blkLocations.length-1].getHosts()));

}

} else if (length != 0) { //如果文件不能切分,相应的会将整个文件作为一个分片。

String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);

splits.add(new FileSplit(path, 0, length, splitHosts));

} else {

//Create empty hosts array for zero length files

splits.add(new FileSplit(path, 0, length, new String[0]));

}

}

LOG.debug("Total # of splits: " + splits.size());

return splits.toArray(new FileSplit[splits.size()]);

}

//真正计算分片大小的地方。

protected long computeSplitSize(long goalSize, long minSize,

                                       long blockSize) {

    return Math.max(minSize, Math.min(goalSize, blockSize));

  }

综上所述,对于MR的输入文件采用的方式是通过FileInputFormat类来进行数据的切分,在切分之前,是通过isSplitable方法来判断是否可以切分,若不能切分,则会将整个文件作为一个分片作为输入。因此,若有业务需求需要对应文件不能进行切分的话,可以将isSplitable方法方位false即可。

这里还需要注意一个问题,倘若你的文件都是小文件的话,对应的getSplits方法也不会对其进行切分的。一般情况小文件指的是其大小小于对应hadoop中HDFS的块的大小(128M)。

Hadoop的数据输入的源码解析的更多相关文章

  1. 【大数据】深入源码解析Map Reduce的架构

    这几天学习了MapReduce,我参照资料,自己又画了两张MapReduce的架构图. 这里我根据架构图以及对应的源码,来解释一次分布式MapReduce的计算到底是怎么工作的. ​话不多说,开始! ...

  2. Java ThreadLocal 的使用与源码解析

    GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/ ThreadLocal 主要解决的是每个线程绑定自己的值,可 ...

  3. 谷歌BERT预训练源码解析(一):训练数据生成

    目录预训练源码结构简介输入输出源码解析参数主函数创建训练实例下一句预测&实例生成随机遮蔽输出结果一览预训练源码结构简介关于BERT,简单来说,它是一个基于Transformer架构,结合遮蔽词 ...

  4. [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader

    [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 目录 [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 0x00 摘要 0x01 ...

  5. [源码解析] PyTorch 流水线并行实现 (3)--切分数据和运行时系统

    [源码解析] PyTorch 流水线并行实现 (3)--切分数据和运行时系统 目录 [源码解析] PyTorch 流水线并行实现 (3)--切分数据和运行时系统 0x00 摘要 0x01 分割小批次 ...

  6. HDFS源码解析:教你用HDFS客户端写数据

    摘要:终于开始了这个很感兴趣但是一直觉得困难重重的源码解析工作,也算是一个好的开端. 本文分享自华为云社区<hdfs源码解析之客户端写数据>,作者: dayu_dls. 在我们客户端写数据 ...

  7. Fresco源码解析 - DataSource怎样存储数据

    Fresco源码解析 - DataSource怎样存储数据 datasource是一个独立的 package,与FB导入的guava包都在同一个工程内 - fbcore. datasource的类关系 ...

  8. [源码]解析 SynchronousQueue 上界,下界.. 数据保存和数据传递. 堵塞队列. 有无频繁await?

     简析SynchronousQueue.LinkedBlockingQueue(两个locker,更快),ArrayBlockingQueue(一个locker,读写都竞争)     三者都是bloc ...

  9. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

随机推荐

  1. System.Data.SqlClient.SqlError:无法对过程'XXX' 执行 删除,因为它正用于复制。消息 3724,级别 16

    遇到这么一个错误,想对[northwindcs]这个数据库搭建复制,从B服务器发布订阅到C服务器,采用备份还原进行初始化,但是在数据库还原时,报无法对过程‘XXX’执行删除,因为它正用于复制.表面上一 ...

  2. C# 拆箱与装箱 要严格控制,数量多起来严重影响效率

    int i = 5; object o = i; int j = (int)o; IComparer x = 5; 1. o的对象必须为一个引用,而数字5不是,则发生了装箱: 运行时将在堆上创建一个包 ...

  3. VB模拟键盘输入的N种方法

    VB模拟键盘输入的N种方法http://bbs.csdn.net/topics/90509805hd378发表于: 2006-12-24 14:35:39用VB模拟键盘事件的N种方法 键盘是我们使用计 ...

  4. SPSS中两种重复测量资料分析过程的比较

    在SPSS中,有两个过程可以对重复测量资料进行分析:一种是一般线性模型的重复度量:一种是混合线性模型,对于同样的数据资料,使用两种过程分析出的内容不大一样,注意是内容而不是结果,只要操作正确,结果应该 ...

  5. SPSS数据分析—信度分析

    测量最常用的是使用问卷调查.信度分析主要就是分析问卷测量结果的稳定性,如果多次重复测量的结果都很接近,就可以认为测量的信度是高的.与信度相对应的概念是效度,效度是指测量值和真实值的接近程度.二者的区别 ...

  6. openxml(二) 添加页眉,页脚

    openxml 中 word 文档的结构是如下图: 其中,页眉是 header,属于headerpart 部件,页脚是footer,属于footerpart 部件,图上还有其他的东西,之后会一一介绍. ...

  7. mongodb语法备份(转)

    mongodb语法 MongoDB的好处挺多的,比如多列索引,查询时可以用一些统计函数,支持多条件查询,但是目前多表查询是不支持的,可以想办法通过数据冗余来解决多表查询的问题. 查询colls所有数据 ...

  8. 投影转换(AE)

    private void btnOK_Click(object sender, EventArgs e) { try { CheckError(); this.checkEdit1.Enabled = ...

  9. (转)JavaScript中的运算符优先级

    JavaScript中的运算符优先级是一套规则.该规则在计算表达式时控制运算符执行的顺序.具有较高优先级的运算符先于较低优先级的运算符执行.例如,乘法的执行先于加法. 下表按从最高到最低的优先级列出J ...

  10. navicat快捷键

    ctrl+R.N 运行窗口sql ctrl+shift+r 运行选择sql ctr+q 打开窗口 ctrl+/   |ctrl+shift+/ 注释   | 解除 ctrl+L 删除一行的内容 ctr ...