Hadoop多文件输出之MultipleOutputFormat和MultipleOutputs

本文介绍如何使用MapReduce的MultipleOutputs功能将数据按特定字段输出到不同的文件夹,通过实例演示具体步骤,并解决默认输出文件的问题。

直到目前,我们看到的所有MapReduce作业都输出一组文件。但是,在一些场合下,经常要求我们输出多组文件或者把一个数据集分为多个数据集更为方便;比如将一个log里面属于不同业务线的日志分开来输出,并且交给相关的业务线。

用过旧API的人应该知道,旧API中有org.apache.hadoop.mapred.lib.MultipleOutputFormat和org.apache.hadoop.mapred.lib.MultipleOutputs两个重要的类,但是由于旧版本的MultipleOutputFormat是基于行的划分而MultipleOutputs是基于列的划分。所以在新的API中就剩下了MultipleOutputs(mapreduce包中)类,这个类合并了旧API中的MultipleOutputFormat和MultipleOutputs的功能,同时新版的类库中已经不存在MultipleOutputFormat类了,因为MultipleOutputs都有它的功能了,还要它干嘛。

下面我们通过一个例子来深入体会新版API中的MultipleOutputs的功能。

测试数据:

  1. 3070818, 1963, 1096,, "US", "IN",, 1,, 441, 6, 69,, 4,, 0.625,,,,,,,
  2. 3070819, 1963, 1096,, "US", "TN",, 4,, 12, 6, 63,, 0,,,,,,,,,
  3. 3070820, 1963, 1096,, "GB", "",, 2,, 12, 6, 63,, 0,,,,,,,,,
  4. 3070821, 1963, 1096,, "US", "IL",, 2,, 15, 6, 69,, 1,, 0,,,,,,,
  5. 3070822, 1963, 1096,, "US", "NY",, 2,, 401, 1, 12,, 4,, 0.375,,,,,,,
  6. 3070823, 1963, 1096,, "US", "MI",, 1,, 401, 1, 12,, 8,, 0.6563,,,,,,,
  7. 3070824, 1963, 1096,, "US", "IL",, 1,, 401, 1, 12,, 5,, 0.48,,,,,,,
  8. 3070825, 1963, 1096,, "US", "IL",, 1,, 401, 1, 12,, 7,, 0.6531,,,,,,,
  9. 3070826, 1963, 1096,, "US", "IA",, 1,, 401, 1, 12,, 1,, 0,,,,,,,
  10. 3070827, 1963, 1096,, "US", "CA",, 4,, 401, 1, 12,, 2,, 0.5,,,,,,,
  11. 3070828, 1963, 1096,, "US", "CT",, 2,, 16, 5, 59,, 4,, 0.625,,,,,,,
  12. 3070829, 1963, 1096,, "FR", "",, 3,, 16, 5, 59,, 5,, 0.48,,,,,,,
  13. 3070830, 1963, 1096,, "US", "NH",, 2,, 16, 5, 59,, 0,,,,,,,,,
  14. 3070831, 1963, 1096,, "US", "CT",, 2,, 16, 5, 59,, 0,,,,,,,,,

#需求:按第四列(国家)将这些信息输出到不同的文件夹中。


实现代码:

  1. public class MultipleOutputsTest {
  2. // 定义输入路径
  3. private static final String INPUT_PATH = "hdfs://liaozhongmin:9000/multipleOutput/multipleOutput_data";
  4. // 定义输出路径
  5. private static final String OUT_PATH = "hdfs://liaozhongmin:9000/out";
  6. public static void main(String[] args) {
  7. try {
  8. // 创建配置信息
  9. Configuration conf = new Configuration();
  10. // 创建文件系统
  11. FileSystem fileSystem = FileSystem.get( new URI(OUT_PATH), conf);
  12. // 如果输出目录存在,我们就删除
  13. if (fileSystem.exists( new Path(OUT_PATH))) {
  14. fileSystem.delete( new Path(OUT_PATH), true);
  15. }
  16. // 创建任务
  17. Job job = new Job(conf, MultipleOutputsTest.class.getName());
  18. //1.1 设置输入目录和设置输入数据格式化的类
  19. FileInputFormat.setInputPaths(job, INPUT_PATH);
  20. job.setInputFormatClass(TextInputFormat.class);
  21. //1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型
  22. job.setMapperClass(MultipleOutputsMapper.class);
  23. job.setMapOutputKeyClass(NullWritable.class);
  24. job.setMapOutputValueClass(Text.class);
  25. //1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应)
  26. job.setPartitionerClass(HashPartitioner.class);
  27. job.setNumReduceTasks( 0);
  28. //1.4 排序
  29. //1.5 归约
  30. //2.1 Shuffle把数据从Map端拷贝到Reduce端。
  31. //2.2 指定Reducer类和输出key和value的类型
  32. //job.setReducerClass(MyReducer.class);
  33. job.setOutputKeyClass(NullWritable.class);
  34. job.setOutputValueClass(Text.class);
  35. //2.3 指定输出的路径和设置输出的格式化类
  36. FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));
  37. job.setOutputFormatClass(TextOutputFormat.class);
  38. // 提交作业 退出
  39. System.exit(job.waitForCompletion( true) ? 0 : 1);
  40. } catch (Exception e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. public static class MultipleOutputsMapper extends Mapper<LongWritable, Text, NullWritable, Text>{
  45. //定义MultipleOutputs
  46. private MultipleOutputs<NullWritable,Text> multipleOutputs = null;
  47. @Override
  48. protected void setup(Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {
  49. //初始化MultipleOutputs对象
  50. multipleOutputs = new MultipleOutputs<NullWritable,Text>(context);
  51. }
  52. protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException,
  53. InterruptedException {
  54. //把结果直接写出去(注意这里不是context而是MultipleOutps的对象)
  55. multipleOutputs.write(NullWritable.get(), value, generateFileName(value));
  56. }
  57. /**
  58. * 自定义一个产生文件名的方法
  59. * @param value
  60. * @return
  61. */
  62. private String generateFileName(Text value){
  63. //对字符串进行切分
  64. String[] splits = value.toString().split( ",");
  65. //截取国家的字符串
  66. String country = splits[ 4].substring( 1, 3);
  67. //返回一个包含国家的文件名
  68. return country + "/";
  69. }
  70. /**
  71. * 用完MultipleOutputs之后一定要关闭
  72. */
  73. @Override
  74. protected void cleanup(Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {
  75. multipleOutputs.close();
  76. }
  77. }
  78. }
程序运行结果:


如上图所示,我们的结果按国家写在了不同的目录下,但是有个奇怪的问题就是,在输出结果中还有两个以part开头的文件,里面什么内容也没有,这是怎么回事呢?原因是他们都是程序的默认输出文件,而我们自定义的输出格式不能以part开头,那么我们如何去掉这两个不太和谐的文件呢?其实很简单,在main()函数中加入以下一行代码:

LazyOutputFormat.setOutputFormatClass(job, TextOutputFormat.class);
如果加入了上面一行代码,请同时注释掉你代码中下面一行代码(如果有)

job.setOutputFormatClass(TextOutputFormat.class);

我们再来看看程序运行结果:


文章来自:过往记忆http://www.iteblog.com/archives/848


另外附上两篇文章:

http://my.oschina.net/leejun2005/blog/94706

http://tydldd.iteye.com/blog/2053867

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值