一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情。
Random 经常被我们用来生成随机数,但是我们要想用好 Random 可不是一个简单的问题,我们先来看一下一道经典的面试题。
private static final Random RANDOM =new Random();
static int random1(int n){
return n>=0?RANDOM.nextInt(n):RANDOM.nextInt(Math.abs(n));
}
static int random2(int n){
return Math.abs(RANDOM.nextInt())%n;
}
public static void main(String[] args) {
int n=1000;
int di=0;
int low=0;
for (int i=0;i<100000000;i++){
if(random1(n)<Math.abs(n/2)){
di++;
}
if(random2(n)<Math.abs(n/2)){
low++;
}
}
System.out.println(di);
System.out.println(low);
}
上面这两个方法 random1 和 random2 相同吗?如果不相同,情说出为什么,如果相同,也请说出为什么。 上面这段代码乍一看看不出来对吧,我们先运行这段代码看一下输出结果:
两个方法生成的随机数大部分都位于前区,因此最终输出的结果相差不大。但是你以为这两个方法是相同的吗?如果你真的这么认为那你就错了,下面我们把前面代码段中的 main 方法稍微改造一下。
public static void main(String[] args) {
int n=2*(Integer.MAX_VALUE/3);
int di=0;
int low=0;
for (int i=0;i<100000000;i++){
if(random1(n)<Math.abs(n/2)){
di++;
}
if(random2(n)<Math.abs(n/2)){
low++;
}
}
System.out.println(di);
System.out.println(low);
}
这时我们再运行代码看一下输出结果:
通过输出结果我们可以看出 random1 方法产生的随机数比较均匀,但是 random2 方法产生的随机数有一大部分都位于前半部分,这是为什么呢?这主要是 nextInt 这个方法导致的。nextInt 方法存在三个缺点,首先如果输入的 n 不是 2 的乘法的话,那么部分数字就会频繁的重复,尤其是在 n 特别大的情况下,因此在上面的 main 方法中 n 的值被乘以了 2。其次如果 n 的数字特别小那么它产生的随机数也会有很大的可能重复。最后在极少的情况下会返回一个不在指定范围内的随机数或者出现报错情况,例如 n 的值是 -2147483648 (int 类型最小值)那么程序就会报错,这是因为 Math.abs 方法遇到 int类型的最小值后发生了溢出(int 类型最大值是 2147483647),溢出后又变回了 -2147483648。关于 Math.abs 溢出讲了这么多,我们不如来看一下 Math.abs 方法的源码,这样你就明白我前面所说得了。
public static int abs(int a) {
return (a < 0) ? -a : a;
}
ic int abs(int a) { return (a < 0) ? -a : a; }
最后我要说明的是这篇文章我前面所写的例子是自定义随机数的例子,一般来说在开发中我们不推荐使用自定义随机,一般我们会使用 Java API 或第三发库自带的随机数生成工具,比如 Java 中的 Random 。