在Java中,我们不免需要生成随机数,而且可能需要生成在某个范围内的随机数。今天在公司就有一个需求是:在一个List<Integer>的list中,随机选取5%的随机数出来。和我的导师商讨之后,确定了一种方案,希望和大家分享。如果大家有更好的解决方案,请给小编评论哦。
在Java中,大家可以很容易想到的实现随机数的方法可以采用Random()方法或Math.Random()方法。
使用Random()函数,构造函数有两个:Random()和Random(long seed)。一个是以当前时间为默认种子,一个是以指定的种子进行随机。使用Math.Random()方法可以产生一个[0.0,1.0)区间的double类型的随机值,根据需要的随机数的范围,乘以对应的数字后,再强制类型转换为int类型即可得到随机数。
第三种方法可以通过取得当前系统的毫秒数,对所需要的范围进行取模运算,得到随机值。
上述三种方法对于单个的随机数都可以实现。但是我需要生成指定个数的随机数列,可能使用循环的效率就没有看起来那么高了。如果在若干次的随机数中,运气很不好,产生的随机数命中了同一个数字,难么就白白浪费掉了多次CPU资源。
比如在上面所说的场景,我现在有一个List<Integer>类型的list,其中有5000条数据,我需要随机选出其中的5%的数字,即250条数据,其实就是选出250个下标。容易想到的方法是通过while循环、for循环或者do{}while循环,每次使用Random()或者Math.Random()产生一个随机数,如果这个随机数没有产生过,则选择,否则进行下一次循环。
上述方法存在一个问题:如果在多次循环的过程中,都产生了一个随机数,那么循环的次数就势必远远多余250次。那么有没有方法可以在有限次的循环内完成呢?
使用java.util.Collection#shuffle()函数,该函数的作用是对集合进行重新打乱(随机排序)。jdk源码如下,其实本质是利用了swap较好函数对list元素进行打乱。最后取得list数组中的前n个元素,实现随机数队列的效果。该方法可以在有限次循环中完成元素的打乱,是取得随机数队列的一种思路。
public static void shuffle(List<?> list, Random rnd) {
int size = list.size();
if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
for (int i=size; i>1; i--)
swap(list, i-1, rnd.nextInt(i));
} else {
Object arr[] = list.toArray();
// Shuffle array
for (int i=size; i>1; i--)
swap(arr, i-1, rnd.nextInt(i));
// Dump array back into list
// instead of using a raw type here, it's possible to capture
// the wildcard but it will require a call to a supplementary
// private method
ListIterator it = list.listIterator();
for (int i=0; i<arr.length; i++) {
it.next();
it.set(arr[i]);
}
}
}