自定义的InputFormat和自定义分区

用一个案例来讲述怎样自定义实现InputFormat和OutputFormat

需求:

将多个小文件合并成一个SequenceFile文件
(SequenceFile文件是Hadoop用来存储二进制形式的key-value对的文件格式,SequenceFile里面存储着多个文件,存储的形式为文件路径+名称为key,文件内容为value)
package com.jee.inputformat;

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;


// 直接继承FileInputFormat而不是InputFormat
// 原因是如果继承InputFormat 我们需要重写两个方法 一个是切片方法 另一个是将读取的内容转化成key-value
// 而我们之间继承InputFormat的子类FileInputFormat 它已经实现了切片方法(就是默认的FIF切片方法) 在我们这个案例也适用  我们只需要实现将读取的内容转化成key-value即可
// FileInputFormat的参数类型  是map任务接受到的类型  根据案例要求是 文件名和文件内容
// 所以参数适用 Text 和 BytesWritable (因为有些文件不一定是字符串 所以最好不使用Text 而是用BytesWritable 字节)
public class WholeFileInputFormat extends FileInputFormat<Text, BytesWritable> {

    //根据需求  我们的文件 不论大小 都不能被切片 直接返回false
    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        return false;
    }

    public RecordReader createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        return new WholeFileRecordReader();
    }
}

package com.jee.inputformat;

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;

//自定义的RecordReader 主要功能是将切片完之后的数据内容变成我们想要的key-value形式(key:文件的路径和名称,value:文件的内容)

public class WholeFileRecordReader extends RecordReader<Text, BytesWritable> {

    //标记文件是否已经读完
    private boolean readOver = false;

    //返回的Key值-->Text
    private Text key = new Text();
    //返回的value值-->ByteWritable
    private BytesWritable value = new BytesWritable();

    //读取文件的输入流
    private FSDataInputStream inputStream;
    //文件切片
    private FileSplit split = new FileSplit();

    //初始化 在框架开始的时候调用一次
    public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        //我们要读取一整个文件 我们需要开流
        //init的时候开流 close的时候关流
        //参数中的 inputSplit就是我们拿到的切片 taskAttemptContext就是我们任务的所有信息

        //强制转化切片类型至文件切片
        split = (FileSplit) inputSplit;
        //通过切片获取路径
        Path path = split.getPath();
        //通过路径获取文件系统(configuration不能随意new一个 要使用当前context中的配置)
        FileSystem fileSystem = path.getFileSystem(taskAttemptContext.getConfiguration());
        //开流
        inputStream = fileSystem.open(path);
    }

    //判断是否还能读取下一组kv值
    public boolean nextKeyValue() throws IOException, InterruptedException {
        if(!readOver){
            readOver = !readOver;
            //具体读文件的流程
            //读key
            key.set(split.getPath().toString());
            //读value
            //一次性读取完整个文件内容 所以byte数组的大小就是文件的大小
            byte[] bytes = new byte[(int)split.getLength()];
            inputStream.read(bytes);
            value.set(bytes,0,bytes.length);
            return true;
        }else {
            return false;
        }
    }

    //返回当前的Key
    public Text getCurrentKey() throws IOException, InterruptedException {
        return key;
    }

    //返回当前的Value
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return value;
    }

    //显示进度 (0~1) 1代表100%
    public float getProgress() throws IOException, InterruptedException {
        return readOver ? 1 : 0;
    }

    //关闭
    public void close() throws IOException {

        //关流
        IOUtils.closeStream(inputStream);
    }
}

package com.jee.inputformat;


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;

import java.io.IOException;

//如果Mapper和Reducer什么事都没有干 只是将拿到的文件原封不动的输出 那么可以不用写  有默认的
public class WholeFileDriver {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //获取Job
        Job job = Job.getInstance(new Configuration());

        //设置Job的类路径
        job.setJarByClass(WholeFileDriver.class);

        //设置Map和Reduce的输入和输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(BytesWritable.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(BytesWritable.class);

        //将自定义的InputFormat和OutputFormat注册到job中
        job.setInputFormatClass(WholeFileInputFormat.class);
        job.setOutputFormatClass(SequenceFileOutputFormat.class);

        //设置输入输出路径
        FileInputFormat.setInputPaths(job,new Path("d:/Hadoop/input"));
        FileOutputFormat.setOutputPath(job,new Path("d:/Hadoop/output"));

        //提交
        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);

    }
}


自定义分区

分区是在map任务执行完之后 在shuffle过程内执行的 默认就是一个 但是我们可以设置多个分区 分区的数量就是我们输出文件的数量

package com.jee.partition;

import com.jee.flow.FlowBean;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

//自定义的分区实现类继承Partitioner类 (参数就是Map任务输入的key-value类型)
public class MyPartitioner extends Partitioner<Text, FlowBean> {

    //设置分区
    public int getPartition(Text text, FlowBean flowBean, int i) {
        String key = text.toString();
        switch (key.substring(0,3)){
            case "137" :{
                return 0;
            } case "138" :{
                return 1;
            } case "139" :{
                return 2;
            } case "182" :{
                return 3;
            } default :{
                return 4;
            }
        }
    }
}

package com.jee.partition;

import com.jee.flow.FlowBean;
import com.jee.flow.FlowDriver;
import com.jee.flow.FlowMapper;
import com.jee.flow.FlowReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 java.io.IOException;

//这个的MapReduce和序列化类的例子中的MapReduce是一样的 所以我们可以复用MapReduce 只需要修改一下driver的分区
public class PartitionerDriver {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.获取job实例
        Job job = Job.getInstance(new Configuration());

        //2.设置类路劲
        job.setJarByClass(FlowDriver.class);

        //3.设置Mapper和Reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        //4.设置输入输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        //5.设置输入输出路径
        FileInputFormat.setInputPaths(job,new Path("D:/Hadoop/input"));
        FileOutputFormat.setOutputPath(job,new Path("D:/Hadoop/output"));

        //设置ReduceTasks的数量
        job.setNumReduceTasks(5);

        //注册我们自定义的分区
        job.setPartitionerClass(MyPartitioner.class);

        //6.提交
        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0: 1);
    }
}

发布了53 篇原创文章 · 获赞 0 · 访问量 1937

猜你喜欢

转载自blog.csdn.net/XXuan_/article/details/105014345