|NO.Z.00044|——————————|BigDataEnd|——|Hadoop&MapReduce.V17|——|Hadoop.v17|InputFormat机制|自定义InputFormat|
一、[InputFormat机制之自定义InputFormat]
### --- 自定义InputFormat
~~~ HDFS还是MapReduce,在处理小文件时效率都非常低,但又难免面临处理大量小文件的场景,
~~~ 此时,就需要有相应解决方案。可以自定义InputFormat实现小文件的合并。
### --- 需求
~~~ 将多个小文件合并成一个SequenceFile文件(SequenceFile文件是Hadoop用来
~~~ 存储二进制形式的key-value对的文件格式),SequenceFile里面存储着多个文件,
~~~ 存储的形式为文件路径+名称为key,文件内容为value。
### --- 结果
~~~ 得到一个合并了多个小文件的SequenceFile文件
二、整体思路
### --- 整体思路
~~~ 定义一个类继承FileInputFormat
~~~ 重写isSplitable()指定为不可切分;重写createRecordReader()方法,
~~~ 创建自己的RecorderReader对象
~~~ 改变默认读取数据方式,实现一次读取一个完整文件作为kv输出;
~~~ Driver指定使用的InputFormat类型

三、编程代码
### --- 创建项目:sequence
### --- 自定义InputFormat
package com.yanqi.mr.sequence;
//自定义inputformat读取多个小文件合并为一个SequenceFile文件
//SequenceFile文件中以kv形式存储文件,key--》文件路径+文件名称,value-->文件的整个内容
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
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;
//TextInputFormat中泛型是LongWritable:文本的偏移量, Text:一行文本内容;指明当前inputformat的输出数据类型
//自定义inputformat:key-->文件路径+名称,value-->整个文件内容
public class CustomInputFormat extends FileInputFormat<Text, BytesWritable> {
//重写是否可切分
@Override
protected boolean isSplitable(JobContext context, Path filename) {
//对于当前需求,不需要把文件切分,保证一个切片就是一个文件
return false;
}
//recordReader就是用来读取数据的对象
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
CustomRecordReader recordReader = new CustomRecordReader();
//调用recordReader的初始化方法
recordReader.initialize(split, context);
return recordReader;
}
}
### --- 自定义RecordReader
package com.yanqi.mr.sequence;
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;
import java.io.IOException;
//负责读取数据,一次读取整个文件内容,封装成kv输出
public class CustomRecordReader extends RecordReader<Text, BytesWritable> {
private FileSplit split;
//hadoop配置文件对象
private Configuration conf;
//定义key,value的成员变量
private Text key = new Text();
private BytesWritable value = new BytesWritable();
//初始化方法,把切片以及上下文提升为全局
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
this.split = (FileSplit) split;
conf = context.getConfiguration();
}
private Boolean flag = true;
//用来读取数据的方法
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
//对于当前split来说只需要读取一次即可,因为一次就把整个文件全部读取了。
if (flag) {
//准备一个数组存放读取到的数据,数据大小是多少?
byte[] content = new byte[(int) split.getLength()];
final Path path = split.getPath();//获取切片的path信息
final FileSystem fs = path.getFileSystem(conf);//获取到文件系统对象
final FSDataInputStream fis = fs.open(path); //获取到输入流
IOUtils.readFully(fis, content, 0, content.length); //读取数据并把数据放入byte[]
//封装key和value
key.set(path.toString());
value.set(content, 0, content.length);
IOUtils.closeStream(fis);
//把再次读取的开关置为false
flag = false;
return true;
}
return false;
}
//获取到key
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
//获取到value
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
//获取进度
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
//关闭资源
@Override
public void close() throws IOException {
}
}
### --- Mapper
package com.yanqi.mr.sequence;
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;
import java.io.IOException;
//负责读取数据,一次读取整个文件内容,封装成kv输出
public class CustomRecordReader extends RecordReader<Text, BytesWritable> {
private FileSplit split;
//hadoop配置文件对象
private Configuration conf;
//定义key,value的成员变量
private Text key = new Text();
private BytesWritable value = new BytesWritable();
//初始化方法,把切片以及上下文提升为全局
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
this.split = (FileSplit) split;
conf = context.getConfiguration();
}
private Boolean flag = true;
//用来读取数据的方法
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
//对于当前split来说只需要读取一次即可,因为一次就把整个文件全部读取了。
if (flag) {
//准备一个数组存放读取到的数据,数据大小是多少?
byte[] content = new byte[(int) split.getLength()];
final Path path = split.getPath();//获取切片的path信息
final FileSystem fs = path.getFileSystem(conf);//获取到文件系统对象
final FSDataInputStream fis = fs.open(path); //获取到输入流
IOUtils.readFully(fis, content, 0, content.length); //读取数据并把数据放入byte[]
//封装key和value
key.set(path.toString());
value.set(content, 0, content.length);
IOUtils.closeStream(fis);
//把再次读取的开关置为false
flag = false;
return true;
}
return false;
}
//获取到key
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
//获取到value
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
//获取进度
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
//关闭资源
@Override
public void close() throws IOException {
}
}
### --- Reducer
package com.yanqi.mr.sequence;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class SequcenReducer extends Reducer<Text, BytesWritable, Text, BytesWritable> {
@Override
protected void reduce(Text key, Iterable<BytesWritable> values, Context context) throws IOException, InterruptedException {
//输出value值(文件内容),只获取其中第一个即可(只有一个)
context.write(key, values.iterator().next());
}
}
### --- Driver
package com.yanqi.mr.sequence;
import com.yanqi.mr.wc.WordCountDriver;
import com.yanqi.mr.wc.WordCountMapper;
import com.yanqi.mr.wc.WordCountReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class SequenceDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
/*
1. 获取配置文件对象,获取job对象实例
2. 指定程序jar的本地路径
3. 指定Mapper/Reducer类
4. 指定Mapper输出的kv数据类型
5. 指定最终输出的kv数据类型
6. 指定job处理的原始数据路径
7. 指定job输出结果路径
8. 提交作业
*/
// 1. 获取配置文件对象,获取job对象实例
final Configuration conf = new Configuration();
final Job job = Job.getInstance(conf, "SequenceDriver");
// 2. 指定程序jar的本地路径
job.setJarByClass(SequenceDriver.class);
// 3. 指定Mapper/Reducer类
job.setMapperClass(SequcenMapper.class);
job.setReducerClass(SequcenReducer.class);
// 4. 指定Mapper输出的kv数据类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
// 5. 指定最终输出的kv数据类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
//设置使用自定义InputFormat读取数据
job.setInputFormatClass(CustomInputFormat.class);
FileInputFormat.setInputPaths(job, new Path("E:\\data\\小文件\\sequence\\input")); //指定读取数据的原始路径
// 7. 指定job输出结果路径
FileOutputFormat.setOutputPath(job, new Path("E:\\data\\小文件\\sequence\\output")); //指定结果数据输出路径
// 8. 提交作业
final boolean flag = job.waitForCompletion(true);
//jvm退出:正常退出0,非0值则是错误退出
System.exit(flag ? 0 : 1);
}
}
四、编译打印
### --- 编译打印
~~~ 输入输出配置参数
~~~ 编译打印


Walter Savage Landor:strove with none,for none was worth my strife.Nature I loved and, next to Nature, Art:I warm'd both hands before the fire of life.It sinks, and I am ready to depart
——W.S.Landor
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通