蓄水池抽样基于MapReduce的实现

</pre>    问题:现在有一个很大的数据,假设有几千万条但不知道具体有多少条,如何在只遍历一次的情况下,随机取出其中K条数据?<p></p><p>    思路:可以将此问题抽象为蓄水池抽样问题。即,先把读取到的前K条数据放入列表中,对于第K+1个对象,以K/(K+1)的概率选择该对象;对于第K+2个对象,以K/(K+2)的概率选择该对象;以此类推,以K/M的概率选择第M个对象(M>K)。如果M被选中,则随机替换列表中的一个对象。如果数据总量N无穷大,则每个对象被选中的概率将均为K/N。</p><p>    </p><p>    设计Mapper:</p><p>    首先要在setup中初始化K的值,也就是随机抽样的个数,然后在map中记录此刻传进来的值在数据流中的位置row,如果row小于K,就将此条数据放入列表中;如果row大于K,则随机生成一个0到row之间的数m,如果m小于K,则将此条数据替换列表中第m条数据,否则不替换。</p><p>   当所有数据经过map后就得到了一个大小为K的列表,这个列表就是我们随机得到的数据。如果数据量小于一个split的大小,则可以省略Reduce过程,直接在cleanup中输出到HDFS。</p><p></p><pre name="code" class="java">public class MyMapper extends Mapper<Object, Text, Text, NullWritable>{
	Logger log = LoggerFactory.getLogger(MyMapper.class);
	private int row = 0;
	private int k=0;
	private ArrayList<Text> result = new ArrayList<>();
	@Override
	protected void setup(Mapper<Object, Text, Text, NullWritable>.Context context)
			throws IOException, InterruptedException {
		k = context.getConfiguration().getInt("k", 3);
	}
	@Override
	protected void map(Object key, Text value, Context context)
			throws IOException, InterruptedException {
		row++;
		if(row <= k){
			result.add(new Text(value)); 
		}
		else{
			int p = randI(row);
			if(p < k){
				result.set(p, new Text(value));
			}
		}
	}
	/***
	 * 
	 * @param max
	 * @return
	 */
	Random random = new Random();
	private int randI(int max) {
		return random.nextInt(max);
	}
	@Override
	protected void cleanup(Context context)
			throws IOException, InterruptedException {
		for(int i=0;i<result.size();i++)
			context.write(result.get(i),NullWritable.get());
		
		
	}
}
    设计Reduce:

    由于数据量非常大,假设我们有m个map,则经过Mapper之后,我们会得到一个m*K大小的列表到Reduce中。因此,只需在Reduce中编写从m*K的列表中随机选取K条数据即可。

public class MyReducer extends Reducer<Text, NullWritable, Text, NullWritable>{
	private int row = 0;
	private int k=0;
	private ArrayList<Text> result = new ArrayList<>();
	@Override
	protected void setup(Context context)
			throws IOException, InterruptedException {
		k = context.getConfiguration().getInt("k", 3);
	}
	@Override
	protected void reduce(Text key, Iterable<NullWritable> values,
			Context context) throws IOException, InterruptedException {
		row++;
		if(row <= k){
			result.add(new Text(key)); 
		}
		else{
			int p = randI(row);
			if(p < k){
				result.set(p, new Text(key));
			}
		}
	}
	/***
	 * 
	 * @param max
	 * @return
	 */
	Random random = new Random();
	private int randI(int max) {
		return random.nextInt(max);
	}
	@Override
	protected void cleanup(Context context)
			throws IOException, InterruptedException {
		for(int i=0;i<result.size();i++)
			context.write(result.get(i),NullWritable.get());
	}
}
    可以观察到,reduce的代码和map的代码基本一致,因此,当数据量小于一个block(128M)(没有手动设置split大小的情况下),可以只写一个map即可。由于问题中是假设的数据量非常大,所以在这里需要添加上reduce。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值