Hadoop(17)自定义输入类

自定义Inputformat类

mapreduce框架当中已经给我们提供了很多的文件输入类,用于处理文件数据的输入,如果以上提供的文件数据类还不够用的话,我们也可以通过自定义InputFormat来实现文件数据的输入

案例需求

现在有大量的小文件,我们通过自定义InputFormat实现将小文件的内容全部读取,然后输出成为一个SequenceFile格式的大文件,进行文件的合并。

案例分析

案例需求的示意图如下:

image-20200213173408994

自定义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);
    }

}

posted @ 2020-08-25 23:58  Whatever_It_Takes  阅读(304)  评论(0编辑  收藏  举报