Hadoop(17)自定义输入类
自定义Inputformat类
mapreduce
框架当中已经给我们提供了很多的文件输入类,用于处理文件数据的输入,如果以上提供的文件数据类还不够用的话,我们也可以通过自定义InputFormat
来实现文件数据的输入
案例需求
现在有大量的小文件,我们通过自定义InputFormat
实现将小文件的内容全部读取,然后输出成为一个SequenceFile
格式的大文件,进行文件的合并。
案例分析
案例需求的示意图如下:
自定义
inputformat
类要实现的读取数据效果是:
- 无论读取的文件是多大的,都是只对应一个切片,一个
maptask
,因此在自定义的inputformat
类里要设置:文件不可被分割成多块多个切片来读取。- 一个
maptask
一次性读取某一个文件的所有内容,生成键值对,键值对的value
是该文件的所有内容,可序列化类型是BytesWritable
,键值对的key
无所谓,设置为NullWritable
即可。然后再把键值对传给map()
处理。
参考
TestInputFormat
类:
继承了
FileInputFormat
类重写了
createRecordReader
方法,这个方法就是用来 按行读取 文件内容,返回一个键值对为<LongWritable, Text>
类型的record
的。因此,我们的自定义类也要重写该方法。public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) { String delimiter = context.getConfiguration().get("textinputformat.record.delimiter"); byte[] recordDelimiterBytes = null; if (null != delimiter) { recordDelimiterBytes = delimiter.getBytes(); } return new LineRecordReader(recordDelimiterBytes); //从这里可知是按行读取 //仿照LineRecordReader类定义我们自己的类 }
因为我们的案例要求不是按行读取的,而是一次性读取一个文件的所有内容,返回的
record
的键值对类型为<NullWritable,BytesWritable>
,value
是一个文件的所有内容。因此我们要仿照LineRecordReader类定义我们自己的RecordReader类。重写了
isSplitable()
方法,该方法是用来确定文件是否可以被切分成多个分片用多个maptask
执行的。因此,我们也要重写该方法,并且我们案例要求不可切分,方法内容为return false。
Map阶段逻辑:
输入到Map阶段的键值对的类型是`<NullWritable,BytesWritable>`,就是我们们自定义的`InputFormat`读取文件生成的`kv`对。 `map`阶段输出的键值对类型为`<Text,BytesWritable>`--》<文件名,文件内容>
步骤1:定义自己的RecordReader类
仿照LineRecordReader
类,创建一个类并继承RecordReader
类,该类是用来读取文件的。
package com.jimmy.day04;
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.NullWritable;
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 java.io.File;
import java.io.IOException;
public class MyRecordReader extends RecordReader {
//设定要读取的分片
private FileSplit fsplit;
private Configuration conf;
//设定变量,用来保存当前value
private BytesWritable valueBtw;
//初始化
@Override
public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
fsplit=(FileSplit)inputSplit;
conf=taskAttemptContext.getConfiguration();
valueBtw= new BytesWritable();
}
//标记一下分片有没有被读取,默认是false;
private boolean flag=false;
//该方法作用是:RecordReader读取分片时,先判断是否有下一个kv对,有则将kv读出来,且返回true,无就返回false
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if (!flag){
//获取分片数据长度
long len=fsplit.getLength();
//新建数组存放分片数据
byte[] contentArr=new byte[(int)len];
//获取分片位置
Path path=fsplit.getPath();
//获取hdfs文件系统
FileSystem fs=path.getFileSystem(conf);
//创建输入流
FSDataInputStream instream=fs.open(path);
//读取分片所有内容,把输入流输出到contentArr里,从第0个位置开始,读取长度为分片数据的长度
IOUtils.readFully(instream,contentArr,0,(int)len);
//设定键值对的value
valueBtw.set(contentArr,0,(int)len);
//防止再次读取
flag=true;
return true;
}
return false;
}
//获取当前的key
@Override
public Object getCurrentKey() throws IOException, InterruptedException {
return NullWritable.get();
}java
//获取当前的value
@Override
public Object getCurrentValue() throws IOException, InterruptedException {
return valueBtw;
}
//返回读取进度
@Override
public float getProgress() throws IOException, InterruptedException {
return flag ? 1.0f : 0.0f;
}
//释放资源
@Override
public void close() throws IOException {
}
}
步骤2:定义自己的MyInputFormat类
package com.jimmy.day04;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import java.io.IOException;
public class MyInputFormat extends FileInputFormat <NullWritable,BytesWritable>{
@Override
public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
//新建对象
MyRecordReader mrr=new MyRecordReader();
//初始化
mrr.initialize(inputSplit,taskAttemptContext);
return mrr;
}
//文件不可有多个分片
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
}
步骤3:定义map逻辑
package com.jimmy.day04;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
public class MyMap extends Mapper<NullWritable, BytesWritable, Text,BytesWritable> {
/**
*
* @param key
* @param value :小文件的全部内容
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(NullWritable key, BytesWritable value, Context context) throws IOException, InterruptedException {
//获取切片
FileSplit inputsplit=(FileSplit)context.getInputSplit();
//获取文件名
String name=inputsplit.getPath().getName();
//输出键值对
context.write(new Text(name),value);
}
}
步骤4:定义组装类和main()方法
package com.jimmy.day04;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
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.output.SequenceFileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class Assem extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
Job job = Job.getInstance(super.getConf(), "mergeSmallFile");
job.setInputFormatClass(MyInputFormat.class);
MyInputFormat.addInputPath(job,new Path("file:///F://test3"));
job.setMapperClass(MyMap.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
//没有reduce。但是要设置reduce的输出的k3 value3 的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
//将我们的文件输出成为sequenceFile这种格式
job.setOutputFormatClass(SequenceFileOutputFormat.class);
SequenceFileOutputFormat.setOutputPath(job,new Path("file:///F://test2//divoutput2"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(), new Assem(), args);
Sysjavatem.exit(run);
}
}