TreeMap存储前K个数据
如何存储前K个数据是TopK问题的一大核心问题,这里采用Java中TreeMap来进行存储。TreeMap的实现是红黑树算法的实现,红黑树又称红-黑二叉树,它首先是一棵二叉树,它具体二叉树所有的特性,同时红黑树更是一棵自平衡的排序二叉树。平衡二叉树必须具备如下特性:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个等等子节点,其左右子树的高度都相近。平衡二叉树图如下图1所示。红黑树顾名思义就是:节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。具有下列特性:1.节点是红色或黑色。2.根节点是黑色。3.每个叶子节点都是黑色的空节点(NIL节点)。4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)。5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。如下图2所示。
在TreeMap的put()的实现方法中主要分为两个步骤,第一:构建排序二叉树,第二:平衡二叉树。为了平衡二叉树,往往需要进行左旋和右旋以及着色操作。这些操作的目的都是为了维持平衡,保证二叉树是有序的,可以帮助我们实现有序的效果,即数据的存储是有序的。
实验环境
搭建实验环境所需操作系统为Ubuntu15,所用软件包括jdk1.7.0,Hadoop-2.9.0。相关的实验环境配置等问题是按照实验课进行配置的,此处不再赘述。Hadoop的启动,进入/usr/local/Hadoop/sbin目录下,通过./start-all.sh命令启动Hadoop。输入jps查看启动是否成功。如下图所示为启动Hadoop截图。
实验数据为存有一百万海量数据的squence.txt文档。通过TOP-K找出其中的前K大或者前K小的数。文档内容如图所示。具体附于实验代码的data文件夹中。数据来源:http://pan.baidu.com/s/1qWt4WaS
实验过程
实验以求100万海量数据的前K个最大的数据为例。进行map函数和reduce函数的编写。
(1)map阶段
通过map方法将数据构造成大小小于K的红黑树,在每次map后判断树的大小和K的大小,当红黑树的数据量大于K时,取出最大的数。在map方法结束后会执行cleanup方法,该方法将map任务中的前K个数据传入reduce任务中.
(2)reduce阶段
在reduce方法中,依次将map方法中传入的K个数据放入TreeMap中,并依靠红黑色的平衡特性来维持数据的有序性。从而将K个数据利用红黑树的firstKey方法按从大到小或者利用红黑树的lastKey方法按从小到大的顺序排列。从而求出前K个数。在run方法中,判断输出路径是否已存在,如果已存在,则删除。此外还设置自定义Mapper和Reducer以及输出目录等。
代码部分:
import java.net.URI;
import java.util.TreeMap;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.GzipCodec;
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;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class MyTopKNumJob extends Configured implements Tool {
/**
* @author SCX
*/
public static class MyMapper extends
Mapper<LongWritable, Text, NullWritable, LongWritable> {
public static final int K =8 ;
private TreeMap<Long, Long> tm = new TreeMap<Long, Long>();
protected void map(
LongWritable key,
Text value,
Mapper<LongWritable, Text, NullWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
try {
long temp = Long.parseLong(value.toString().trim());
tm.put(temp, temp);
if (tm.size() > K) {
// 如果是求topk个最大的那么使用下面的语句
tm.remove(tm.firstKey());
//如果是求topk个最小的那么使用下面的语句
// tm.remove(tm.lastKey());
}
} catch (Exception e) {
context.getCounter("TopK", "errorLog").increment(1L);
}
};
protected void cleanup(
org.apache.hadoop.mapreduce.Mapper<LongWritable, Text, NullWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
for (Long num : tm.values()) {
context.write(NullWritable.get(), new LongWritable(num));
}
};
}
public static class MyReducer extends
Reducer<NullWritable, LongWritable, NullWritable, LongWritable> {
public static final int K = 8;
private TreeMap<Long, Long> tm = new TreeMap<Long, Long>();
protected void reduce(
NullWritable key,
java.lang.Iterable<LongWritable> values,
Reducer<NullWritable, LongWritable, NullWritable, LongWritable>.Context context)
throws java.io.IOException, InterruptedException {
for (LongWritable num : values) {
tm.put(num.get(), num.get());
if (tm.size() > K) {
//tm.remove(tm.firstKey());
// 如果是求topk个最小的那么使用下面的语句
tm.remove(tm.lastKey());
}
}
// 按降序即从大到小排列Key集合
for (Long value : tm.descendingKeySet()) {
context.write(NullWritable.get(), new LongWritable(value));
}
};
}
// 输入文件路径
public static String INPUT_PATH = "hdfs://localhost:9000/user/hadoop/sequence.txt";
// 输出文件路径s
public static String OUTPUT_PATH = "hdfs://localhost:9000/user/hadoop/topk8";
@Override
public int run(String[] args) throws Exception {
// 首先删除输出路径的已有生成文件
FileSystem fs = FileSystem.get(new URI(INPUT_PATH), getConf());
Path outPath = new Path(OUTPUT_PATH);
if (fs.exists(outPath)) {
fs.delete(outPath, true);
}
Job job = new Job(getConf(), "TopKNumberJob");
// 设置输入目录
FileInputFormat.setInputPaths(job, new Path(INPUT_PATH));
// 设置自定义Mapper
job.setMapperClass(MyMapper.class);
job.setMapOutputKeyClass(NullWritable.class);
job.setMapOutputValueClass(LongWritable.class);
// 设置自定义Reducer
job.setReducerClass(MyReducer.class);
job.setOutputKeyClass(NullWritable.class);
job.setOutputValueClass(LongWritable.class);
// 设置输出目录
FileOutputFormat.setOutputPath(job, new Path(OUTPUT_PATH));
return job.waitForCompletion(true) ? 0 : 1;
}
public static void main(String[] args) {
Configuration conf = new Configuration();
// map端输出启用压缩
conf.setBoolean("mapred.map.output", true);
// reduce端输出启用压缩
conf.setBoolean("mapred.output", true);
// reduce端输出压缩使用的类
conf.setClass("mapred.output.codec", GzipCodec.class,CompressionCodec.class);
try {
int res = ToolRunner.run(conf, new MyTopKNumJob(), args);
System.exit(res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
实验结果
(1)令K=10,找出一百万海量数据的前10个小数据。实验结果如图所示。
(2)令K=100,找出一百万海量数据的前100个小数据。实验结果如图所示。
(3)令K=8,找出一百万海量数据的前8个大数据。实验结果如图所示。