mapreduce的自定义输入格式

mapreduce自定义输入格式

概念:

  • 当普通的输入格不能满足客户的要求的时候。因为普通的输入格式是将文件的每一行输入的数据作为一个value值然后进行map端的操作。现在有的需求是将数据库中的数据作为一个输入的格式,或者是将一个文件的整体作为一个输入格式等。

举例:

  • 现在有一个需求是将一个目录下的所有小文件读取进来,将文件的整个内容都作为一个value值进行输入。出来map端的值是文件名称作为key值,整个文件内容作为value值。

源码解析:

  • 源码

    public abstract class InputFormat<K, V> {
    
      /** 
       * Logically split the set of input files for the job.  
       * 
       * <p>Each {@link InputSplit} is then assigned to an individual {@link Mapper}
       * for processing.</p>
       *
       * <p><i>Note</i>: The split is a <i>logical</i> split of the inputs and the
       * input files are not physically split into chunks. For e.g. a split could
       * be <i>&lt;input-file-path, start, offset&gt;</i> tuple. The InputFormat
       * also creates the {@link RecordReader} to read the {@link InputSplit}.
       * 
       * @param context job configuration.
       * @return an array of {@link InputSplit}s for the job.
       */
      public abstract 
        List<InputSplit> getSplits(JobContext context
                                   ) throws IOException, InterruptedException;
      
      /**
       * Create a record reader for a given split. The framework will call
       * {@link RecordReader#initialize(InputSplit, TaskAttemptContext)} before
       * the split is used.
       * @param split the split to be read
       * @param context the information about the task
       * @return a new record reader
       * @throws IOException
       * @throws InterruptedException
       */
      public abstract 
        RecordReader<K,V> createRecordReader(InputSplit split,
                                             TaskAttemptContext context
                                            ) throws IOException, 
                                                     InterruptedException;
    
    }
    
  • 解说:

    • 这是最基础的InputFormat类,其中包含了两个方法,第一个方法就是getSplit()方法,和getRecordReader()方法。
    • getSplit():这个方法是获取这个文件的分片信息,必须要实现。
    • getRecordReader():这个方法是具体操作读取文件的方式,也必须得实现。

具体操作

创造一个文件:
  • 三个文件
...........
1.txt
one
...........
2.txt
tow
...........
3.txt
three
...........
书写自定一的文件输入类型
  • 创建新的格式CusFileInputFormat类
/**
 * @description
 * @author: LuoDeSong 694118297@qq.com
 * @create: 2019-06-19 11:06:09
 **/
public class CusFileInputFormat extends FileInputFormat<NullWritable, BytesWritable> {

    //读取的文件是否可被切分,这里的文件小于了128M,所以不需要进行切分,所以设置的值是false,如果需要切分的时候就改成true
    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        return false;
    }

    @Override
    public RecordReader createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {

        CusRecordReader cusRecordReader = new CusRecordReader();
        cusRecordReader.initialize(split,context);
        return cusRecordReader;
    }
}
* 说明:因为没有必要更改别人的文件分片的规则,所以我们可以直接继承InputFormat类的实现类FileInputFormat,他其中已经实现了文件的分片规则。我们需要做的就是重新定义我们读取文件的个是就行也就是重新写一个RecordReader类。为getRecordReader()做准备。
* 参数说明:因为输入的key是整个文件所以就不需偏移量了,所以为NullWritable;因为读取的是整个文件,并且是按照字节的方式来读取的,所以为BytesWritable。
  • 创建新的RecordReader类CusRecordReader:
/**
 * @description
 * @author: LuoDeSong 694118297@qq.com
 * @create: 2019-06-19 11:10:19
 **/
public class CusRecordReader extends RecordReader<NullWritable, BytesWritable> {
    //定义配置类
    private Configuration conf;
    //文件的切片类
    private FileSplit split;
    //是否继续读取文件
    private boolean propress;
    //输出数据的格式什么样的
    private BytesWritable bytesWritable = new BytesWritable();

    @Override
    public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
        this.split = (FileSplit) split;//向下转型
        this.conf = context.getConfiguration();//获取配置文件信息
    }

    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        if (!propress) {
            //1定义缓冲区
            byte[] data = new byte[(int) split.getLength()];

            //要读取的文件就是一个切片
            FileSystem fs = null;
            FSDataInputStream input = null;
            try {
                //获取文件系统的实例
                Path path = split.getPath();
                fs = path.getFileSystem(conf);

                //读取数据
                input = fs.open(path);
                IOUtils.readFully(input, data, 0, data.length);

                bytesWritable.set(data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                IOUtils.closeStream(input);
            }
            propress = true;
            return propress;
        }
        return false;
    }

    @Override
    public NullWritable getCurrentKey() throws IOException, InterruptedException {
        return NullWritable.get();
    }

    @Override
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return bytesWritable;
    }

    //读取的进度
    @Override
    public float getProgress() throws IOException, InterruptedException {
        return this.propress ? 1 : -1;
    }

    @Override
    public void close() throws IOException {

    }
}
* 说明:需要继承最根本的文件读取的类RecordReader,然后重新按照自己的方式来重新其中必须的方法,书写的过程和方式已经在代码中做好了注释。
* 参数说明:和CusFileInputFormat类中的说明是一样的。
  • 编写mapper:
/**
 * @description
 * @author: LuoDeSong 694118297@qq.com
 * @create: 2019-06-19 11:36:06
 **/
public class FileMapper extends Mapper<NullWritable, ByteWritable, Text, ByteWritable> {

    private Text newKey = new Text();

    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        FileSplit inputSplit = (FileSplit) context.getInputSplit();
        String name = inputSplit.getPath().getName();
        newKey.set(name);
    }

    @Override
    protected void map(NullWritable key, ByteWritable value, Context context) throws IOException, InterruptedException {
        context.write(newKey, value);
    }
}
  • 编写reducer:
/**
 * @description
 * @author: LuoDeSong 694118297@qq.com
 * @create: 2019-06-19 11:42:22
 **/
public class FileReducer extends Reducer<Text, ByteWritable, Text, ByteWritable> {
    @Override
    protected void reduce(Text key, Iterable<ByteWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key, values.iterator().next());
    }
}
  • 编写Driver:
public class Driver {

	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		
		Configuration conf=new Configuration();
		
		Job job=Job.getInstance(conf);
		
		job.setJarByClass(Driver.class);
		
		job.setMapperClass(FileMapper.class);
		
		job.setReducerClass(FileReducer.class);
		
		job.setMapOutputKeyClass(Text.class);
		
		job.setMapOutputValueClass(BytesWritable.class);
		
		job.setOutputKeyClass(Text.class);
		
		job.setOutputValueClass(BytesWritable.class);

		job.setInputFormatClass(CusFileInputFormat.class);

		FileInputFormat.setInputPaths(job, new Path(args[0]));
		
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
		
	    boolean result=job.waitForCompletion(true);
	    
	    System.exit(result?0:1);
		
	}
}

总结:

  • 更改输入格式是一个必需的点,我们整个过程实际上就是追溯源码,仿照源码得来的,希望你在大数据的路上越走越好。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MapReduce是一种用于处理大规模数据集的编程模型和软件框架。Hadoop是一个基于MapReduce模型的分布式文件存储和处理系统。在Hadoop中,MapReduce被广泛用于数据处理和分析任务。 自定义二次排序是MapReduce中常见的一种需求,其目的是对MapReduce的输出进行排序。下面我们来介绍一下如何在Linux上使用Hadoop实现自定义二次排序。 1. 准备数据 首先我们需要准备一个数据集,假设我们有一个文本文件,每行包含两个字段,分别为学生姓名和成绩,中间用制表符分隔。例如: ``` Tom 80 Jerry 70 Mike 90 Lucy 85 ``` 2. 编写Mapper代码 自定义二次排序需要进行两次排序,第一次按照学生姓名进行排序,第二次按照成绩进行排序。因此,我们需要在Mapper中将学生姓名和成绩作为Key-Value输出。 我们可以使用TextPair类来存储学生姓名和成绩,代码如下: ``` public class SortMapper extends Mapper<LongWritable, Text, TextPair, Text> { private TextPair pair = new TextPair(); public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] fields = value.toString().split("\t"); pair.set(fields[0], fields[1]); context.write(pair, value); } } ``` 在这段代码中,我们首先将输入的一行数据拆分成学生姓名和成绩两个字段,然后使用TextPair类将它们作为Key输出,原始数据作为Value输出。 3. 编写Partitioner代码 Partitioner用于对Mapper的输出进行分区,以确保相同Key的数据被分配到同一个Reducer中。在自定义二次排序中,我们需要按照学生姓名进行分区,因此我们可以使用HashPartitioner来进行分区,代码如下: ``` public class SortPartitioner extends Partitioner<TextPair, Text> { public int getPartition(TextPair key, Text value, int numPartitions) { return (key.getFirst().hashCode() & Integer.MAX_VALUE) % numPartitions; } } ``` 在这段代码中,我们使用HashPartitioner将学生姓名的HashCode和Partition数取模来确定数据被分配到哪个Reducer中。 4. 编写GroupComparator代码 GroupComparator用于将相同学生姓名的数据分配到同一个Reducer中,代码如下: ``` public class SortGroupComparator extends WritableComparator { protected SortGroupComparator() { super(TextPair.class, true); } public int compare(WritableComparable a, WritableComparable b) { TextPair pair1 = (TextPair) a; TextPair pair2 = (TextPair) b; return pair1.getFirst().compareTo(pair2.getFirst()); } } ``` 在这段代码中,我们重载了compare方法,用于比较两个Key的学生姓名是否相同。 5. 编写SortComparator代码 SortComparator用于对每个Reducer中的数据进行排序,按照成绩从大到小排序,代码如下: ``` public class SortComparator extends WritableComparator { protected SortComparator() { super(TextPair.class, true); } public int compare(WritableComparable a, WritableComparable b) { TextPair pair1 = (TextPair) a; TextPair pair2 = (TextPair) b; int cmp = pair1.getFirst().compareTo(pair2.getFirst()); if (cmp != 0) { return cmp; } return -pair1.getSecond().compareTo(pair2.getSecond()); } } ``` 在这段代码中,我们首先比较两个Key的学生姓名是否相同,如果相同则比较成绩,否则直接返回姓名比较结果。 6. 编写Reducer代码 Reducer用于对Mapper的输出进行聚合和处理。在自定义二次排序中,我们只需要将每个学生的成绩按照从高到低的顺序输出即可,代码如下: ``` public class SortReducer extends Reducer<TextPair, Text, Text, Text> { public void reduce(TextPair key, Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text value : values) { context.write(key.getFirst(), value); } } } ``` 在这段代码中,我们首先输出学生姓名,然后按照原始数据的顺序输出。 7. 编写Driver代码 最后,我们需要编写Driver代码来启动MapReduce作业。代码如下: ``` public class SortDriver extends Configured implements Tool { public int run(String[] args) throws Exception { Job job = Job.getInstance(getConf()); job.setJarByClass(SortDriver.class); job.setMapperClass(SortMapper.class); job.setPartitionerClass(SortPartitioner.class); job.setGroupingComparatorClass(SortGroupComparator.class); job.setSortComparatorClass(SortComparator.class); job.setReducerClass(SortReducer.class); job.setMapOutputKeyClass(TextPair.class); job.setMapOutputValueClass(Text.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); return job.waitForCompletion(true) ? 0 : 1; } public static void main(String[] args) throws Exception { int exitCode = ToolRunner.run(new SortDriver(), args); System.exit(exitCode); } } ``` 在这段代码中,我们首先创建一个Job实例,然后设置Mapper、Partitioner、GroupComparator、SortComparator和Reducer等类。最后,我们指定输入路径和输出路径,并启动作业。 以上就是在Linux上使用Hadoop实现自定义二次排序的流程。通过这个例子,您可以了解到如何在Linux系统上使用MapReduce编程模型和Hadoop分布式文件存储和处理系统来处理大规模数据集。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值