Data Mining

Hadoop的InputFormats和OutputFormats

InputFormat

InputFormat类用来产生InputSplit,并把它切分成record。

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;
}

有时候一个文件虽然大于一个block size,但是你不希望它被切分,一种办法上把mapred.min.split.size提高到比该文件的长度还要大,另一个办法是自定义FileInputFormat的子类,让isSplitable()方法返回false。

InputSplit是由客户端计算出来提交给JobTracker的,JobTracker把它存放在Configuration中,mapper可以从中获取split的信息。

map.input.file   String    被map处理的文件的路径

map.input.start   long       从split的开头偏移的字节量

map.input.length  long    split的长度

FileInputFormat

有3个类实现了InputFormat接口中:DBInputFormat, DelegatingInputFormat, FileInputFormat。FileInputFormat是所有数据来自文件的InputFormat的基类。

通过FileInputFormat可以指定输入文件有哪些,而且FileInputFormat实现了InputFormat中的getSplits()方法,getRecordReader()则需要由FileInputFormat的子类来实现。

Path可以代表一个文件、一个目录、文件或目录的集合(通过使用glob)。

如果把一个目录传递给FileInputFormat.addPath(),这不是递归的模式,即该目录下的目录是不会作为输入数据源的,并且这种情况下会引发错误。解决办法是使用glob或filter来仅选择目录下的文件。下面看一下如何使用FileInputFormat的静态方法设置filter:

static void setInputPathFilter(Job job, Class<? extends PathFilter> filter) 

即使你不设置filter,默认情况下FileInputFormat也会把隐藏文件过滤掉。

你或许会问,通过调用FileInputFormat.addPath()得到了很多输入文件,FileInputFormat如何把它们切分成input split呢?事实上,FileInputFormat只切分大文件,对于小于一个HDFS block的文件它独自产生一个input split。HDFS block的大小可以通过ds.block.size来设置。

CombineFileInputFormat

前文已经说过很多小文件带来2个麻烦:namenode上要维护很多文件的metadata,容易造成namenode内存溢出;很多小文件就会产生很多inputsplit,从而产生很多map task,而map task的启动、销毁、监控都会带来额外的开销。使用Hadoop Archive(HAR)可以解决第一个问题,但不能解决第2个问题,因为对MapReduce来说看到的还归档前的文件。使用SequenceFile不仅可以合并文件,还有压缩的作用,节省磁盘空间。但是如果你现在已经有了诸多小文件,在进行MapReduce之前把它们合并成一个SequenceFile可能还得不偿失,这时候你可以用CombineFileInputFormat。CombineFileInputFormat并没的把原文件合并,它只是对于MapReduce来说产生了较少的InputSplit(通过把多个文件打包到一个inputsplit中--当然是把邻近的文件打包进同一个split,这样map task离它要处理的文件都比较近)。使用CombineFileInputFormat时你可能需要调节split的大小,一个split的最大(默认为Long.MAX_VALUE)和最小长度(默认为1B)可以通过mapred.min.split.size和mapred.max,split.size来设置。

CombineFileInputFormat是抽象类,它里面有一个抽象方法需要子类来实现:

abstract  RecordReader<K,V>    createRecordReader(InputSplit split, TaskAttemptContext context)

自定义InputFormat

TextInputFormat是默认的InputFormat,一行为一个record,key是该行在文件中的偏移量,value是该行的内容。

经常我们需要自定义InputFormat。我们希望一行为一个record,但key不是该在文件中的偏移量,而是行号,下面的代码是读取存储了一个二维矩阵的文件,我们需要知道每行的行号,从而知道矩阵中的行索引。

package basic;

import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.util.LineReader; public class MatrixLineRecordReader extends RecordReader<IntWritable, Text> { private LineReader in;
private int lineno=0;
private boolean more=true;
private IntWritable key=null;
private Text value=null; private Log LOG=LogFactory.getLog(MatrixLineRecordReader.class); @Override
public void initialize(InputSplit inputSplit,
TaskAttemptContext context) throws IOException, InterruptedException {
FileSplit split=(FileSplit)inputSplit;
Configuration conf=context.getConfiguration();
Path file=split.getPath();
FileSystem fs=file.getFileSystem(conf);
FSDataInputStream fileIn=fs.open(file);
in=new LineReader(fileIn,conf);
} @Override
public boolean nextKeyValue() throws IOException, InterruptedException {
LOG.info("line number is "+lineno);
if(key==null)
key=new IntWritable();
if(value==null)
value=new Text();
int readsize=in.readLine(value);
LOG.info("line content is "+value.toString());
if(readsize==0){
more=false;
return false;
}
key.set(lineno);
lineno++;
return true;
} @Override
public IntWritable getCurrentKey() throws IOException, InterruptedException {
return key;
} @Override
public Text getCurrentValue() throws IOException,
InterruptedException {
return value;
} @Override
public float getProgress() throws IOException, InterruptedException {
if(more)
return 0.0f;
else
return 100f;
} @Override
public void close() throws IOException {
in.close();
}
}

有一个细节问题:如果一个split刚好从一个record的中间切开怎么办?放心,这种情况下split会自动扩容以容纳下最后一条record,也就是说split会比block size略长。

Multiple Inputs

有时数据来源于不同的文件,它们包含的信息相同,但格式(InputFormat)不同,我们需要用不同的RecordReader来解析。这时可以调用MultipleInputs的静态方法:

static void    addInputPath(Job job, Path path, Class<? extends InputFormat> inputFormatClass)

甚至有时候对不同的数据来源需要使用不同的map来处理:

static void    addInputPath(Job job, Path path, Class<? extends InputFormat> inputFormatClass, Class<? extends Mapper> mapperClass)

注意不同的map output要有相同的类型,因为它们要被同一个reducer处理,且这些mapper Emit(key,value)的顺序是不可预知的。这时候就不要再调用FileInputFormat.addInputPath()和job.setMpperClass()了。

自定义OutputFormat

TextOutputFormat是默认的OutputFormat,每条record占一行,其key 和value都是Text类型,用tab隔开(这个分隔符可以通过mapred.textoutputformat.separator设置)。你还可设置key或者value为空,只要设为类型NullWritable(这是一个单例模式)就可以了,此时分隔符也就不会被输出了。

一般我们并不需要实现一个FileOutputFormat的子类(这之间还要实现一个RecordWriter的子类),而只需要实现一个Writable的子类,重写它的toString()方法就可以了--这种方法确实更简单一些。

Multiple Outputs

一般情况下一个reducer产生一个输出文件(文件大小与block size无关),命名为part-r-xxxxx,xxxxx是从0开始的序号。FileOutputFormat及其子类产生的输出文件都在同一个目录下。有时候你可能想控制输出文件的名称,或者想让一个reducer产生多个输出文件。

MultipleOutputFormat(按行划分到不同的文件)

假如MapReduce输出了如下的一些record:

Jim   Class1    male
Lili Class2 female
Tom Class1 male
Mei Class2 female

我们想按class把record输出到不同的文件,只需要自定义一个MultipleOutputFormat类,然后把设置为OutputFormat就可以了。

public class PertitionByClass extends MultipleTextOutputFormat<NullWritable,Text>{
@Override
protected String generateFileNameForKeyValue(NullWritable key,Text value,String filenmae){
String []arr=value.toString().split("\\s+");
return arr[1];
}
}
job.setOutputFormat(PertitionByClass);

MultipleTextOutputFormat继承自MultipleOutputFormat。

写这篇博客时间比较久了,MultipleTextOutputFormat只在hadoop0.21之前的版本能用,在新版中需要用MultipleOutputs,具体参见军哥的博客

MultipleOutputs(按列分到不同的文件

如果想输出为2个文件,一个文件存放“姓名,班级”,另一个文件存放“姓名,性别”。

在Reducer中

public class MyReducer extends Reducer<NullWritable, Text, NullWritable, Text> {

    private MultipleOutputs<NullWritable, Text>  mos;
@Override
public void setup(Context context){
mos=new MultipleOutputs<NullWritable, Text>(context);
}
@Override
public void reduce(NullWritable key,Iterable<Text> values,Context context)throws IOException,InterruptedException{
while(values.iterator().hasNext()){
Text text=values.iterator().next();
String []arr=text.toString().split("\\s+");
String nc=arr[0]+","+arr[1];    //姓名,班级
String nf=arr[0]+","+arr[2];    //姓名,性别
mos.write("nc", NullWritable.get(), new Text(nc));
mos.write("nf", NullWritable.get(), new Text(nf));
}
}
@Override
public void cleanup(Context context)throws IOException,InterruptedException{
mos.close();
}
}

然后在driver中调用MultipleOutputs的静态方法addNamedOutput()

MultipleOutputs.addNamedOutput(job,"nc",TextOutputFormat.class,NullWritable.class,Text.class);
MultipleOutputs.addNamedOutput(job,"nf",TextOutputFormat.class,NullWritable.class,Text.class);

输出文件名中包含"nc"或"nf"作为区别。

解释一下上面用到的几个函数

static void    addNamedOutput(Job job, String namedOutput, Class<? extends OutputFormat> outputFormatClass, Class<?> keyClass, Class<?> valueClass)
Adds a named output for the job. <K,V> void write(String namedOutput, K key, V value)
Write key and value to the namedOutput. <K,V> void write(String namedOutput, K key, V value, String baseOutputPath)
Write key and value to baseOutputPath using the namedOutput.
原文来自:博客园(华夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun

(转)Hadoop的InputFormats和OutputFormats的更多相关文章

  1. Spark External Datasets

    Spark能够从任何支持Hadoop的存储源来创建RDD,包括本地的文件系统,HDFS,Cassandra,Hbase,Amazon S3等.Spark支持textFile.SequenceFiles ...

  2. SparkR安装部署及数据分析实例

    1. SparkR的安装配置 1.1.       R与Rstudio的安装 1.1.1.           R的安装 我们的工作环境都是在Ubuntu下操作的,所以只介绍Ubuntu下安装R的方法 ...

  3. Apache Spark 2.2.0 中文文档 - 快速入门 | ApacheCN

    快速入门 使用 Spark Shell 进行交互式分析 基础 Dataset 上的更多操作 缓存 独立的应用 快速跳转 本教程提供了如何使用 Spark 的快速入门介绍.首先通过运行 Spark 交互 ...

  4. Spark编程模型

    主要参考: Spark官方文档:http://spark.apache.org/docs/latest/programming-guide.html 炼数成金PPT:02Spark编程模型和解析 本文 ...

  5. Spark (Python版) 零基础学习笔记(一)—— 快速入门

    由于Scala才刚刚开始学习,还是对python更为熟悉,因此在这记录一下自己的学习过程,主要内容来自于spark的官方帮助文档,这一节的地址为: http://spark.apache.org/do ...

  6. Apache Spark 2.2.0 中文文档

    Apache Spark 2.2.0 中文文档 - 快速入门 | ApacheCN Geekhoo 关注 2017.09.20 13:55* 字数 2062 阅读 13评论 0喜欢 1 快速入门 使用 ...

  7. spark-shell使用指南. - 韩禹的博客

    在2.0版本之前,Spark的主要编程接口是RDD(弹性分布式数据集),在2.0之后,则主推Dataset,他与RDD一样是强类型,但更加优化.RDD接口仍然支持,但为了更优性能考虑还是用Datase ...

  8. Hadoop Compatibility in Flink

    18 Nov 2014 by Fabian Hüske (@fhueske) Apache Hadoop is an industry standard for scalable analytical ...

  9. Hadoop 之面试题

    颜色区别: 蓝色:hive,橙色:Hbase.黑色hadoop 请简述hadoop怎样实现二级排序. 你认为用Java,Streaming,pipe 方式开发map/reduce,各有哪些优缺点: 6 ...

随机推荐

  1. 《C++ Primer》之面向对象编程(一)

    面向对象编程基于三个基本概念:数据抽象.继承和动态绑定.//动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 面向对象编程的关键思想是多态性(polymorphism). ...

  2. 下拉的DIV+CSS+JS二级树型菜单

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. GlusterFS常用命令小结

    # /etc/init.d/glusterd start # /etc/init.d/glusterd stop # /etc/init.d/glusterd status 2.       开机自动 ...

  4. opewrt上传文件

    设备上运行的openwrt,当tftp和ftp都无法使用时,可以使用命令scp在两台linux设备上copy文件. 当设备启动起来后,输入命令: scp hbg@192.168.2.32:/home/ ...

  5. Unity 的ICallHander在C#项目中实现注入

    项目包引用: Install-Package Unity.Interception 创建项目的接口和其实类: public interface ICalculator { double Calcula ...

  6. MVC4相关Razor语法以及Form表单(转载)

    Razor的布局(Layout) 默认建的工程都自带的了一个_ViewStart.cshtml文件,文件里面的代码如下: @{ Layout = "~/Views/Shared/_Layou ...

  7. 老鸟需要知道的一些php系统类函数

    作为一个老手级别的php程序员,知道下面几个php系统级别的函数,不足为多吧!获取系统信息和调试程序的时候应该能用的上! PHP系统类函数 assert函数:检查assertion声明是否错误 ext ...

  8. network: 思科-华为光模块

    思科-华为光模块的分类比较   摘要:本文介绍:思科GLC-SX-MM,GLC-LH-SM光模块等产品参数与图片,华为光模块的型号与分类等知识. 光模块分类与介绍 一.思科厂家 1.多模光模块 型号: ...

  9. linux c中select使用技巧

    1.select函数作为定时器使用    it_value.tv_sec = 0;    it_value.tv_usec = 100000:    select(1,NULL,NULL,NULL,& ...

  10. MongoDB用户

    MongoDB  增加用户 删除用户  修改用户  读写权限 只读权限,   MongoDB用户权限分配的操作是针对某个库来说的.--这句话很重要.   1. 进入ljc 数据库:       use ...