多个小文件合并,
一个key-value,value是小文件的所有内容。
套路:模仿org.apache.hadoop.mapreduce.lib.input.LineRecordReader
和org.apache.hadoop.mapreduce.lib.input.TextInputFormat
- 把输入控件设置成自定义的控件类
job.setInputFormatClass(MyAllFileInputFormat.class);
实现自定义控件继承FileInputFormat
public class MyAllFileInputFormat extends FileInputFormat<Text, LongWritable>{
@Override
public RecordReader<Text, LongWritable> createRecordReader(InputSplit split, TaskAttemptContext context)
throws IOException, InterruptedException {
//实例化一个
MyAllFileRecodReader reader = new MyAllFileRecodReader();
//split参数和context都是框架自动传入的,把这两个参数传给reader进行处理,以便获取相关信息
reader.initialize(split, context);
return reader;
}
/**
* 给定的文件名可拆分吗?返回false确保单个输入文件不会被分割。以便Mapper处理整个文件。
*/
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
}
实现自定义RecordReader。
重点:
- nextKeyValue()方法中实现读取文件逻辑。
- 在initialize()可以接受到FileInputFormat组件中传来的InputSplit和Context对象,可以从其中获取到文件信息,和配置文件等一些列信息。
public class MyAllFileRecodReader extends RecordReader<Text, LongWritable>{
//用于存储文件系统输入流
private FSDataInputStream open = null;
//保存文件长度
private int fileSplitLength = 0;
/**
* 当前的MyAllFileRecodReader读取到的一个key-value
*/
private Text key = new Text();
private LongWritable value = new LongWritable();
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
//通过InputSplit对象获取文件路径
FileSplit fileSplit = (FileSplit)split;
Path path = fileSplit.getPath();
//获取文件长度
fileSplitLength = (int)fileSplit.getLength();
//通过context对象获取到配置文件信息,通过配置文件获取到一个当前文件系统
Configuration configuration = context.getConfiguration();
FileSystem fs = FileSystem.get(configuration);
//获取文件系统的一个输入流
open = fs.open(path);
}
/**
* 已读标记
* 如果为false,表示还没有进行读取
* 在需求中一个mapTask只处理一个小文件,一个mapTask最终只需要读取一次就完毕
* 如果一个文件读取完毕了,那么就把isRead这个变量标记为true
*/
private boolean isRead = false;
/**
* 实现读取规则:逐文件读取
*/
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
//如果没有读取过文件就进入
if(!isRead){
//准备一个字节数组长度为文件的长度
byte[] buffer = new byte[fileSplitLength];
//一次性把真个文件读入字节数组中
IOUtils.readFully(open, buffer);
//把读取到的文件传给key
key.set(buffer, 0, fileSplitLength);
//设置已读标记为true
isRead = true;
//返回读取一个文件成功标记
return true;
}else{
return false;
}
}
//获取key的方法
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
//获取当前value值
@Override
public LongWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
/**
* 获取数据的处理进度的
*/
@Override
public float getProgress() throws IOException, InterruptedException {
//已读为真返回1.0,没有读返回0
return isRead ? 1.0F : 0F;
}
@Override
public void close() throws IOException {
//安静地关闭输入流
IOUtils.closeQuietly(open);
}
}
- 最后记得修改mapper中的输入key和value的类型
public static class InputMapper extends Mapper<Text, LongWritable, Text, NullWritable>{
@Override
protected void map(Text key, LongWritable value, Context context) throws IOException, InterruptedException {
context.write(key, NullWritable.get());
}
}
自定义输出组件
给定数据条目为:math,liutao,86(课程名,姓名,考试分数)。
需求:分数超过60的同学输出到文件夹jige中,低于60分的输出到文件夹bujige中。
实现方法:改写数据输出组件的输出规则。
套路:模仿org.apache.hadoop.mapreduce.lib.output.TextOutputFormat
public class MyMultipePathOutputFormat extends FileOutputFormat<Text, NullWritable>{
@Override
public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
//获得当前的文件系统传给自定义的RecordWriter组件
Configuration configuration = job.getConfiguration();
FileSystem fs = FileSystem.get(configuration);
try {
//返回一个RecordWriter正在处理输出数据的组件
return new MyMutiplePathRecordWriter(fs);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 实现自定义组件MyRecordWriter继承RecordWriter实现核心方法write()
public class MyMutiplePathRecordWriter extends RecordWriter<Text, NullWritable>{
//声明要输出的两个路径
private DataOutputStream out_jige;
private DataOutputStream out_bujige;
public MyMutiplePathRecordWriter(FileSystem fs) throws Exception {
//创建系统输出流
out_jige = fs.create(new Path("E:\\bigdata\\cs\\jige\\my_output_jige.txt"));
out_bujige = fs.create(new Path("E:\\bigdata\\cs\\bujige\\my_output_bujige.txt"));
}
/**
* 实现写出方法,根据需要写出的格式自定义
*/
@Override
public void write(Text key, NullWritable value) throws IOException, InterruptedException {
//接受到的key格式为:course + "\t" + name + "\t" + avgScore
String keyStr = key.toString();
String[] split = keyStr.split("\t");
//获取到平均分字段
double score = Double.parseDouble(split[2]);
//没一行数据加入个换行符
byte[] bytes = (keyStr + "\n").getBytes();
//如果平均分大于60就用DataOutputStream写出到jige目录
if(score >= 60){
out_jige.write(bytes, 0, bytes.length);
}else{//小于60分的写道bujige目录
out_bujige.write(bytes, 0, bytes.length);
}
}
/**
* 在close方法中关闭输出流。
*/
@Override
public void close(TaskAttemptContext context) throws IOException, InterruptedException {
IOUtils.closeQuietly(out_jige);
IOUtils.closeQuietly(out_bujige);
}
}