mapreduce——Top3(TopN)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/a805814077/article/details/102080821

数据自拟

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

猜你喜欢

转载自blog.csdn.net/a805814077/article/details/102080821