CombineTextInputFormat
Hadoop默认的TextInputFormat切片机制是对任务按文件切片,不管文件多小,都会是一个单独的切片,然后交给一个MapTask。如果有大量小文件,就会产生大量MapTask,处理效率非常低。
CombineTextInputFormat用于处理大量小文件,它可以将多个小文件从逻辑上看成一个切片。多个小文件可以交给一个MapTask处理。
虚拟存储过程:
setMaxInputSplitSize设置最大切片大小,假设4M。
如果文件大小小于最大切片大小,则单独划分为一块,如一个2.5M的文件划分为一块。
如果文件大小大于最大切片大小但小于最大切片大小的两倍,则平分为两块(防止出现太小切片),如5M划分两块,各2.5M。
如果文件大小大于最大切片大小的两倍,则以最大值切一块。
例如setMaxInputSplitSize值为4M,输入文件大小为8.02M,则先逻辑上分成一个4M,剩余的大小为4.02M。如果按照4M逻辑划分,就会出现0.02M的小的虚拟存储文件,所以将剩余的4.02M文件切分成(2.01M和2.01M)两个文件。
切片过程:
判断虚拟存储的文件大小是否大于setMaxInputSplitSize值,大于等于则单独形成一个切片。
如果不大于,则跟下一个虚拟存储文件进行合并,形成一个切片。如两个文件,1.5M和3M,形成一个4.5M的切片。
在驱动类添加如下代码,采用CombineTextInputFormat。
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
KeyValueTextInputFormat
每一行被分割符(第一个分隔符)分割为key,value。可以通过在驱动类设置 conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, “\t”) 来设定分割符,默认分割符是 tab("\t")。
在驱动类添加如下代码,采用KeyValueTextInputFormat。
//设定分割符,默认分割符是\t
conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, " ");
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(KeyValueTextInputFormat.class);
NLineInputFormat
每个MapTask处理的InputSplit不再按block块进行划分,而是按指定的行数N来划分。即输入文件的总行数 ÷ N = 切片数,如果不整除,切片数=商+1。.
在驱动类添加如下代码,采用NLineInputFormat。
//设定分割符,默认分割符是\t
NLineInputFormat.setNumLinesPerSplit(job, 3);
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(NLineInputFormat.class);
自定义InputFormat
自定义InputFormat,将多个小文件合并成一个SequenceFile文件(SequenceFile文件是Hadoop用来存储二进制形式的key-value对的文件格式),SequenceFile里面存储着多个文件,key为文件路径+名称为,value为文件内容。
自定义一个类继承FileInputFormat,重写createRecordReader(),创建自定义的RecordReader对象,并初始化initialize。
import java.io.IOException;
import org.apache.hadoop.io.BytesWritable;
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.FileInputFormat;
public class DIYInputFormat extends FileInputFormat<Text, BytesWritable>{
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context)
throws IOException, InterruptedException {
DIYRecordReader reader = new DIYRecordReader();
reader.initialize(split, context);
return reader;
}
}
键是文件的全路径,值是文件内容。nextKeyValue()在Mapper.run()方法调用,每个切片会新创建一个DIYRecordReader对象。
import java.io.IOException;
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.BytesWritable;
import org.apache.hadoop.io.IOUtils;
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;
public class DIYRecordReader extends RecordReader<Text, BytesWritable>{
FileSplit split;
Configuration conf;
Text key = new Text();
BytesWritable value = new BytesWritable();
boolean isProgress = true;
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
this.split = (FileSplit) split;
this.conf = context.getConfiguration();
}
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if (isProgress) {
//1.获取fs对象
Path path = split.getPath();
FileSystem fs = path.getFileSystem(conf);
//2.获取输入流
FSDataInputStream fis = fs.open(path);
//3.拷贝
byte[] buf = new byte[(int) split.getLength()];
IOUtils.readFully(fis, buf, 0, buf.length);
//4.封装key
key.set(path.toString());
//5.封装value
value.set(buf, 0, buf.length);
//6.关闭资源
IOUtils.closeStream(fis);
isProgress = false;
return true;
}
return false;
}
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
@Override
public void close() throws IOException {
}
}
import java.io.IOException;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class DIYInputFormatMapper extends Mapper<Text, BytesWritable, Text, BytesWritable> {
@Override
protected void map(Text key, BytesWritable value, Mapper<Text, BytesWritable, Text, BytesWritable>.Context context)
throws IOException, InterruptedException {
context.write(key, value);
}
}
import java.io.IOException;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class DIYInputFormatReducer extends Reducer<Text, BytesWritable, Text, BytesWritable> {
@Override
protected void reduce(Text key, Iterable<BytesWritable> values,
Reducer<Text, BytesWritable, Text, BytesWritable>.Context context) throws IOException, InterruptedException {
for (BytesWritable value : values) {
context.write(key, value);
}
}
}
驱动类设置InputFormat为自定义的InputFormat。设置OutputFormat为SequenceFileOutputFormat,多个文件输出成一个文件,键为全路径,值为文件内容,访问时输入全路径即可获得内容。
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
public class DIYInputFormatDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
args = new String[] {
"e:/input", "e:/output"};
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(DIYInputFormatDriver.class);
job.setMapperClass(DIYInputFormatMapper.class);
job.setReducerClass(DIYInputFormatReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//设置输入的InputFormat
job.setInputFormatClass(DIYInputFormat.class);
//设置输出的OutputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
InputFormat总结
<K, V> | 切片 | |
---|---|---|
TextInputFormat(默认) | 键是存储该行在整个文件中的起始字节偏移量,LongWritable类型。 值是该行的内容,不包括任何行终止符(换行符和回车键),Text类型。 |
按块的大小切片,本地是32M;集群Hadoop1.0是64M;2.0是128M |
CombineTextInputFormat | 键和值与TextInputFormat一样 | 按给定大小切片 |
KeyValueTextInputFormat | 键是该行第一个分隔符左边的内容。 值是该行第一个分隔符右边的内容。 |
按块的大小切片 |
NLineInputFormat | 键和值与TextInputFormat一样 | 按指定数量的行切块片*自定义InputFormat** |