版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
数据自拟
package com.hadoop.mapreduce.Test4;
/**
* 求十亿(海量)数据中的最小的3个数
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/*
* 思路:
* 要求很多文件中的最小的三个数,我们可以把每个文件中的三个数求出来,
* 然后再在这些数中找最小的数,这样效率就比较高了
*/
public class Top3 {
/*
* map端:
* 可以定义一个集合来存最小的三个数,首先是将集合排序,然后保持集合的长度为3,即在集合的长度为4的时候删除最大的那个数
*/
static class MyMapper extends Mapper<LongWritable, Text, IntWritable, NullWritable> {
// 定义一个集合,这里我们是求最小的数所以用IntWritable
List<Integer> list = new ArrayList<Integer>();
IntWritable mk = new IntWritable();
@Override
protected void map(LongWritable key, Text value,
Mapper<LongWritable, Text, IntWritable, NullWritable>.Context context)
throws IOException, InterruptedException {
// 获取数据
String[] nums = value.toString().split("\t");
for (String s : nums) {
int num = Integer.parseInt(s);
list.add(num);
// 利用工具类排序
Collections.sort(list);
// 删除掉最大的那个数
if (list.size() == 4)
list.remove(3);
}
// 这里不能直接写context,因为我们有多个文件,如果直接在这里写每个文件的每一行就会有三个数
}
// 所以这里我们还要重写cleanup方法,每个文件只取三个数
@Override
protected void cleanup(Mapper<LongWritable, Text, IntWritable, NullWritable>.Context context)
throws IOException, InterruptedException {
// 遍历集合中的数据直接写出
for (Integer i : list) {
mk.set(i);
context.write(mk, NullWritable.get());
}
}
}
static class MyReducer extends Reducer<IntWritable, NullWritable, IntWritable, NullWritable> {
// 定义一个全局的计数器
int count = 0;
@Override
protected void reduce(IntWritable key, Iterable<NullWritable> values,
Reducer<IntWritable, NullWritable, IntWritable, NullWritable>.Context context)
throws IOException, InterruptedException {
// int count = 0;这个计数器一定要写成全局的,不然每次进入reduce方法都是从0开始
/*for (NullWritable v : values) {
count++;
context.write(key, v);
if (count == 3) {
break;
}
}*/
//这里说一下为什么不能像上面注释那样写
/*加入集合中的数据是这样的
* 1<null,null>
* 2<null,null,null>
* 3<null,null>
* 4<null,null,null>
* 如果按注释的写法,假如有四组数第一次会输出第一行两个null里面的key即1、1
* 第二次会输出三个null里面的key,这样就已经输出了1、1、2、2、2
*很显然后面的两个2是没有必要输出的,所以这样写是有问题的
*/
for (NullWritable v : values) {
count++;
// 这样写就没问题
if (count <= 3)
context.write(key, v);
}
/*
* 这样写的话,每遍历一次null就输出一次,遍历三个null刚好输出完
* 就可以输出第一行的两个null,第二行的一个null就可以达到我们想要的结果了
*/
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(Top3.class);
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(NullWritable.class);
Path inpath = new Path("F:\\test2");
FileInputFormat.addInputPath(job, inpath);
Path outpath = new Path("F:\\test\\testout\\");
FileSystem fs = FileSystem.get(conf);
if (fs.exists(outpath)) {
fs.delete(outpath, true);
}
FileOutputFormat.setOutputPath(job, outpath);
job.waitForCompletion(true);
}
}
我的结果:
1
1
2