MapReduce自定义输入输出组件

多个小文件合并,

一个key-value,value是小文件的所有内容。

套路:模仿org.apache.hadoop.mapreduce.lib.input.LineRecordReader
和org.apache.hadoop.mapreduce.lib.input.TextInputFormat

  1. 把输入控件设置成自定义的控件类
job.setInputFormatClass(MyAllFileInputFormat.class);
  1. 实现自定义控件继承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;
    }
}
  1. 实现自定义RecordReader。

    重点:

    1. nextKeyValue()方法中实现读取文件逻辑。
    2. 在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);
    }

}
  1. 最后记得修改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;
    }
}
  1. 实现自定义组件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);
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值