MapReduce自定义RecordReader

一:背景

RecordReader表示以怎样的方式从分片中读取一条记录,每读取一条记录都会调用RecordReader类,系统默认的RecordReader是LineRecordReader,它是TextInputFormat对应的RecordReader;而SequenceFileInputFormat对应的RecordReader是SequenceFileRecordReader。LineRecordReader是每行的偏移量作为读入map的key,每行的内容作为读入map的value。很多时候hadoop内置的RecordReader并不能满足我们的需求,比如我们在读取记录的时候,希望Map读入的Key值不是偏移量而是行号或者是文件名,这时候就需要我们自定义RecordReader。

 

二:技术实现

(1):继承抽象类RecordReader,实现RecordReader的一个实例。

(2):实现自定义InputFormat类,重写InputFormat中的CreateRecordReader()方法,返回值是自定义的RecordReader实例。

(3):配置job.setInputFormatClass()为自定义的InputFormat实例。

 

#需求:统计data文件中奇数行和偶数行的和:

[java]  view plain  copy
 
  1. 10  
  2. 20  
  3. 50  
  4. 15  
  5. 30  
  6. 100  


实现代码如下:

 

MyRecordReader.java:

 

[java]  view plain  copy
 
  1. public class MyRecordReader extends RecordReader<LongWritable, Text>{  
  2.   
  3.     //起始位置(相对整个分片而言)  
  4.     private long start;  
  5.     //结束位置(相对整个分片而言)  
  6.     private long end;  
  7.     //当前位置  
  8.     private long pos;  
  9.     //文件输入流  
  10.     private FSDataInputStream fin = null;  
  11.     //key、value  
  12.     private LongWritable key = null;  
  13.     private Text value = null;  
  14.     //定义行阅读器(hadoop.util包下的类)  
  15.     private LineReader reader = null;  
  16.       
  17.     @Override  
  18.     public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {  
  19.           
  20.         //获取分片  
  21.         FileSplit fileSplit = (FileSplit) split;  
  22.         //获取起始位置  
  23.         start = fileSplit.getStart();  
  24.         //获取结束位置  
  25.         end = start + fileSplit.getLength();  
  26.         //创建配置  
  27.         Configuration conf = context.getConfiguration();  
  28.         //获取文件路径  
  29.         Path path = fileSplit.getPath();  
  30.         //根据路径获取文件系统  
  31.         FileSystem fileSystem = path.getFileSystem(conf);  
  32.         //打开文件输入流  
  33.         fin = fileSystem.open(path);  
  34.         //找到开始位置开始读取  
  35.         fin.seek(start);  
  36.         //创建阅读器  
  37.         reader = new LineReader(fin);  
  38.         //将当期位置置为1  
  39.         pos = 1;  
  40.           
  41.     }  
  42.   
  43.     @Override  
  44.     public boolean nextKeyValue() throws IOException, InterruptedException {  
  45.         if (key == null){  
  46.             key = new LongWritable();  
  47.         }  
  48.         key.set(pos);  
  49.         if (value == null){  
  50.             value = new Text();  
  51.         }  
  52.         if (reader.readLine(value) == 0){  
  53.             return false;  
  54.         }  
  55.         pos ++;  
  56.           
  57.         return true;  
  58.           
  59.     }  
  60.   
  61.     @Override  
  62.     public LongWritable getCurrentKey() throws IOException, InterruptedException {  
  63.         return key;  
  64.     }  
  65.   
  66.     @Override  
  67.     public Text getCurrentValue() throws IOException, InterruptedException {  
  68.         return value ;  
  69.     }  
  70.   
  71.     @Override  
  72.     public float getProgress() throws IOException, InterruptedException {  
  73.           
  74.         return 0;  
  75.     }  
  76.   
  77.     @Override  
  78.     public void close() throws IOException {  
  79.         fin.close();  
  80.           
  81.     }  
  82.   
  83. }  


MyInputFormat.java

 

 

[java]  view plain  copy
 
  1. public class MyInputFormat extends FileInputFormat<LongWritable, Text>{  
  2.   
  3.   
  4.     @Override  
  5.     public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {  
  6.         //返回自定义的RecordReader  
  7.         return new MyRecordReader();  
  8.     }  
  9.   
  10.     /** 
  11.      * 为了使得切分数据的时候行号不发生错乱 
  12.      * 这里设置为不进行切分 
  13.      */  
  14.     protected boolean isSplitable(FileSystem fs, Path filename) {  
  15.         return false;  
  16.     }  
  17.   
  18. }  


MyPartitioner.java

 

 

[java]  view plain  copy
 
  1. public class MyPartitioner extends Partitioner<LongWritable, Text>{  
  2.   
  3.     @Override  
  4.     public int getPartition(LongWritable key, Text value, int numPartitions) {  
  5.         //偶数放到第二个分区进行计算  
  6.         if (key.get() % 2 == 0){  
  7.             //将输入到reduce中的key设置为1  
  8.             key.set(1);  
  9.             return 1;  
  10.         } else {//奇数放在第一个分区进行计算  
  11.             //将输入到reduce中的key设置为0  
  12.             key.set(0);  
  13.             return 0;  
  14.         }  
  15.     }  
  16.   
  17. }  


主类 RecordReaderTest.java

 

 

[java]  view plain  copy
 
  1. public class RecordReaderTest {  
  2.   
  3.     // 定义输入路径  
  4.     private static String IN_PATH = "";  
  5.     // 定义输出路径  
  6.     private static String OUT_PATH = "";  
  7.   
  8.     public static void main(String[] args) {  
  9.   
  10.         try {  
  11.             // 创建配置信息  
  12.             Configuration conf = new Configuration();  
  13.             // 获取命令行的参数  
  14.             String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();  
  15.             // 当参数违法时,中断程序  
  16.             if (otherArgs.length != 2) {  
  17.                 System.err.println("Usage:wordcount<in> <out>");  
  18.                 System.exit(1);  
  19.             }  
  20.   
  21.             // 给路径赋值  
  22.             IN_PATH = otherArgs[0];  
  23.             OUT_PATH = otherArgs[1];  
  24.             // 创建文件系统  
  25.             FileSystem fileSystem = FileSystem.get(new URI(OUT_PATH), conf);  
  26.             // 如果输出目录存在,我们就删除  
  27.             if (fileSystem.exists(new Path(new URI(OUT_PATH)))) {  
  28.                 fileSystem.delete(new Path(new URI(OUT_PATH)), true);  
  29.             }  
  30.   
  31.             // 创建任务  
  32.             Job job = new Job(conf, RecordReaderTest.class.getName());  
  33.             // 打成jar包运行,这句话是关键  
  34.             job.setJarByClass(RecordReaderTest.class);  
  35.             // 1.1 设置输入目录和设置输入数据格式化的类  
  36.             FileInputFormat.setInputPaths(job, IN_PATH);  
  37.             job.setInputFormatClass(MyInputFormat.class);  
  38.   
  39.             // 1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型  
  40.             job.setMapperClass(RecordReaderMapper.class);  
  41.             job.setMapOutputKeyClass(LongWritable.class);  
  42.             job.setMapOutputValueClass(Text.class);  
  43.   
  44.             // 1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应,因为分区为一个,所以reduce的数量也是一个)  
  45.             job.setPartitionerClass(MyPartitioner.class);  
  46.             job.setNumReduceTasks(2);  
  47.   
  48.             // 1.4 排序  
  49.             // 1.5 归约  
  50.             // 2.1 Shuffle把数据从Map端拷贝到Reduce端。  
  51.             // 2.2 指定Reducer类和输出key和value的类型  
  52.             job.setReducerClass(RecordReaderReducer.class);  
  53.             job.setOutputKeyClass(Text.class);  
  54.             job.setOutputValueClass(LongWritable.class);  
  55.   
  56.             // 2.3 指定输出的路径和设置输出的格式化类  
  57.             FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));  
  58.             job.setOutputFormatClass(TextOutputFormat.class);  
  59.   
  60.             // 提交作业 退出  
  61.             System.exit(job.waitForCompletion(true) ? 0 : 1);  
  62.   
  63.         } catch (Exception e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.     }  
  67.   
  68.     public static class RecordReaderMapper extends Mapper<LongWritable, Text, LongWritable, Text> {  
  69.         @Override  
  70.         protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, LongWritable, Text>.Context context) throws IOException,  
  71.                 InterruptedException {  
  72.             // 直接将读取的记录写出去  
  73.             context.write(key, value);  
  74.         }  
  75.     }  
  76.   
  77.     public static class RecordReaderReducer extends Reducer<LongWritable, Text, Text, LongWritable> {  
  78.   
  79.         // 创建写出去的key和value  
  80.         private Text outKey = new Text();  
  81.         private LongWritable outValue = new LongWritable();  
  82.   
  83.         protected void reduce(LongWritable key, Iterable<Text> values, Reducer<LongWritable, Text, Text, LongWritable>.Context context) throws IOException,  
  84.                 InterruptedException {  
  85.               
  86.             System.out.println("奇数行还是偶数行:" + key);  
  87.               
  88.             // 定义求和的变量  
  89.             long sum = 0;  
  90.             // 遍历value求和  
  91.             for (Text val : values) {  
  92.                 // 累加  
  93.                 sum += Long.parseLong(val.toString());  
  94.             }  
  95.   
  96.             // 判断奇偶数  
  97.             if (key.get() == 0) {  
  98.                 outKey.set("奇数之和为:");  
  99.             } else {  
  100.                 outKey.set("偶数之和为:");  
  101.   
  102.             }  
  103.             // 设置value  
  104.             outValue.set(sum);  
  105.   
  106.             // 把结果写出去  
  107.             context.write(outKey, outValue);  
  108.         }  
  109.     }  
  110. }  

程序运行结果:

 

MapReduce自定义RecordReader_第1张图片

MapReduce自定义RecordReader_第2张图片


注:分区数大于2的MR程序要打成jar包才能运行!

你可能感兴趣的:(MapReduce自定义RecordReader)