MapReduce
组件
输入格式 - InputFormat
-
InputFormat发生在Mapper之前,用于对数据进行切分和读取,会将读取到的数据传递给MapTask处理。所以InputFormat读取到的数据是什么格式,Mapper接收到的数据就是什么格式
-
作用
getSplits
:对文件进行切片处理createRecordReader
:创建输入流读取文件
-
默认情况下,MapReduce中使用的输入格式是
TextInputFormat
,TextInputFormat
继承了FileInputFormat
。需要注意的是,FileInputFormat
负责切片,TextInputFormat
负责读取 -
FileInputFormat
切片过程中需要注意的问题:-
默认情况下,minSplitSize=1B,maxSplitSize=Long.MAX_VALUE
-
在MapReduce中,
_
开头的文件默认是隐藏文件不处理 -
如果当前文件是一个空文件,那么这个文件本身就是一个切片
-
在MapReduce中,文件有可切和不可切的区别。大部分情况下,文件是可切的,但如果是压缩文件,那么不一定可切
-
如果文件不可切,那么这整个文件就是一个切片
-
默认情况下,Split和Block等大
-
如果需要调大SplitSize,那么需要调大minSize;如果需要调小SplitSize,那么需要调小maxSize
// 单位是字节 FileInputFormat.setMinInputSplitSize(); FileInputFormat.setMaxInputSplitSize();
-
切片过程中需要
SPLIT_SLOP
,默认值是1.1。当bytesRemaining/splitSize > SPLIT_SLOP
才会继续切片。所以,如果一个文件是520M,那么对应了5个Block(4*128+8),对应了4个Split(3*128+136)
-
-
TextInputFormat
读取过程中需要注意的问题:- TextInputFormat在读取文件之前会先判断文件是否可切:先获取文件的压缩编码,判断压缩编码是否为空。如果压缩编码为空,那么说明不是压缩文件,该文件可切;如果压缩编码不为空,那么判断是否是一个可切分的压缩文件
- 在MapReduce中,默认可切的压缩编码是
BZip2Codec
,后缀是.bz2
- 在读取文件的时候,会先获取每条数据之间的分隔符,默认情况下,分隔符是
\n
。可以通过textinputformat.record.delimiter
来指定每一条数据之间的间隔符号 - 确定好间隔符之后,会创建输入流
LineRecordReader
对象来读取数据 - 默认情况下,是按行读取数据,每一行数据最多允许有
Integer.MAX_VALUE
个字节 - 在读取文件的时候,会判断文件是否是一个压缩文件。如果不是压缩文件,那么直接读取;如果是压缩文件,那么解压之后再读取
- 为了保证数据处理的完整性,除了第一个MapTask以外,其他的MapTask都需要从当前切片的第二行开始,处理到下一个切片的第一行;第一个MapTask要多处理一行;最后一个MapTask会少处理一行数据
-
实际过程中,如果给定的数据和MapReduce提供的输入格式不匹配,那么此时可以考虑自定义输入格式。考虑到文件还需要进行切片,所以可以定义一个类继承
FileInputFormat
,此时只需要考虑怎么读取数据即可 -
多源输入:在MapReduce中,如果需要同时处理多个文件,且多个文件位于多个位置,那么此时需要使用多源输入
- 在多源输入中,输入的文件格式不可以不同,可以给每一个文件单独指定一个InputFormat
- 在多源输入中,如果输出格式不同,或者Mapper逻辑不同,那么此时可以给每一个文件单独指定一个Mapper
输出格式 - OutputFormat
-
OutputFormat
发生在Reducer之后,负责将ReduceTask产生的数据按照指定格式写出到指定位置 -
作用
- 校验输出路径,主要是确定输出路径要不存在!!!
- 提供输出流将数据写出到指定位置
-
默认情况下,MapReduce中使用的输出格式是
TextOutputFormat
,TextOutputFormat
继承了FileOutputFormat
。需要注意的是,FileOutputFormat
负责校验路径,TextOutputFormat
负责写出数据 -
TextOutputFormat
写出数据过程中需要注意的问题:-
写出的时候,会先判断是否要对结果进行压缩
-
获取键值对之间的间隔符号,默认是
\t
。可以通过mapreduce.output.textoutputformat.separator
来指定 -
如果对输出结果进行压缩,那么如果没有指定压缩编码,默认会压缩成
.deflate
包 -
如果需要对输出结果进行压缩,那么需要在入口类中添加
FileOutputFormat.setCompressOutput(job, true); FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
-
-
实际过程中,如果需要指定特殊的输出格式,那么就需要自定义输出格式。需要定义一个类继承
OutputFormat
,但是考虑到还需要校验输出路径是否存在,所以一般是继承FileOutputFormat
-
扩展:多源输出。将数据根据分类,将结果输出到不同的文件中。案例:字符统计。将字母、数字和符号分别放到不同的文件中进行统计
补充
单例模式
-
单例模式(Singleton)是设计模式中最常见、最简单的模式之一 ,属于构建/建造型模式
-
设计模式(design pattern):针对软件开发过程中的某一类问题形成的方案。到目前为止,在软件开发过程中,有上百种设计模式,其中比较常用的有24种
-
单例模式,顾名思义,指的是在全局只存在唯一的一个实例对象
-
单例模式有六种实现方式:饿汉式、懒汉式、单重所、双重锁(DCL)、枚举式、内部类式
-
饿汉式
public class A { // 在类中准备好一个对象,希望所有的使用者都只用准备号的唯一对象 // 饿汉式:在定义对象的时候就初始化对象 // 缺点:无论是否需要使用这个对象,在第一次调用这个类的时候都会初始化这个对象,从而增长了类的加载时间 // 优点:没有线程的并发问题 - 类只加载一次,所以静态对象也只初始化一次 private static final A a = new A(); // 不允许在类外随便创建对象 - 构造函数私有化 private A(){} public static A get(){return a;} }
-
懒汉式
public class A { // 懒汉式:在定义对象的时候不初始化对象,而是在第一次调用方法的时候再初始化这个对象 // 优点:在有需要的时候才会初始化对象,从而缩短了类的加载时间 // 缺点:会产生线程的并发问题 private static A a; private A(){} public static A get(){ if(a == null) a = new A(); return a; } }
-
单重锁
public class A { private static A a; private A(){} public static A get(){ // 优点:保证线程的并发安全 // 缺点:锁本质上就是保证线程的"独占",因此加锁和解锁都需要消耗资源 // 当对象被初始化之后,后续的所有的线程都需要先加锁,然后判断是否为空,再解锁,也就意味着所有的线程都需要经历锁的过程 synchronized(A.class){ if(a == null) a = new A(); } return a; } }
-
双重锁(DCL - Double Check Lock)
public class A { private static A a; private A(){} public static A get(){ if(a == null){ synchronized(A.class){ if(a == null){ synchronized(A.class){ a = new A(); } } } } return a; } }
-
枚举式
enum A{ a }
-
内部类式
public class A { public static class AI{ private static final AI instance = new AI(); } public static AI getInstance(){ return AI.instance; } }
private static final AI instance = new AI();
}public static AI getInstance(){ return AI.instance; }
}
-