主要介绍
- Math.random( )方法
- Random类
- 随机的基本原理
1. Math.random( )方法
最基本的随机莫过于Math类中的random( )方法,它生成一个0~1之间的随机数,类型为double,包括0而不包括1。
那么,Math.random( )中的随机是如何实现的呢?
public static double random() {
return Math.RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
private RandomNumberGeneratorHolder() {
}
}
可以看出,它内部其实是产生了一个Random类型的randomNumber-Generator。也就是说,调用Math中的random( )方法,实际上是调用了Random类的nextDouble( )方法。
那么Random类是怎么产生随机数的呢?
2. Random类
Random类提供了产生各种类型随机数的方法,但是由于其中的方法并非静态,所以想要使用这些方法,必须要创建一个Random实例。
如:
Random random = new Random();
random.nextInt();
random.nextInt(100);
其中,前者产生一个随机的int,可能为正数,也可能为负数
而后者产生一个介于0~100的正数,包括0,不包括100。
当然,除了nextInt( )方法,还有如下方法:
// 生成随机字节,并将其置于给出的字节数组
public void nextBytes(byte[] var1);
// 随机生成一个long
public long nextLong();
// 随机生成一个boolean
public boolean nextBoolean();
// 0~1范围内的随机浮点数,含0不含1
public float nextFloat();
// 0~1范围内的随机浮点数,含0不含1
public double nextDoble();
补充:
关于nextBytes方法
针对源代码进行分析,可得,每次先产生一个32位的int类型的数据(通过nextInt()方法),如果要产生的数组长度大于4,那就将其右移3次,即可得三个数字,填充数组,如果数组中数字数目不够就继续产生int类型的数据;反之,如果数组长度小于4,那么只需将int类型的数据右移小于等于3的次数,即可得数组。
再浅看一眼Random的构造方法
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
public Random(long var1) {
this.haveNextNextGaussian = false;
if (this.getClass() == Random.class) {
this.seed = new AtomicLong(initialScramble(var1));
} else {
this.seed = new AtomicLong();
this.setSeed(var1);
}
}
关于第一个构造方法,会在下面提到。
那么第二个构造方法,可以接受一个long类型的种子。
种子决定了随机产生的序列,种子相同,产生的随机数序列就相同。
那至于为什么要指定种子,当然是,为了随机的复现。
关于随机的原理,下面进行简单的介绍。
随机的原理
Random产生的随机数被称为伪随机数的原因是,那并不是真正意义上的随机数。
伪随机数都是基于一个种子数的,然后每需要一个随机数,就对当前的种子进行一些数学运算,得到一个属,基于这个数得到需要的随机数和新的种子。
因为数学运算是固定的式子,所以只要种子确定了,那么产生的随机数序列就是确定的,确定的随机数当然不算是真正的随机数。不过呢,种子不同,序列就不同,而且每个序列中的数字分布随机而均匀,故而称作是伪随机数。
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
long var0;
long var2;
do {
var0 = seedUniquifier.get();
var2 = var0 * 181783497276652981L;
} while(!seedUniquifier.compareAndSet(var0, var2));
return var2;
}
上即为Random类默认的无参构造方法,不难看出,在未给出种子的情况下,Random类会自动生成一个种子,而这个种子,相较而言,是随机的。
种子是seedUniquifier( ) 和System.nanoTime( )异或的结果,其中seedUniquifier( )是返回当前seedUniquifier和181783497276652981L这个long数据的乘积,然后CAS操作尝试设置旧值var0为更新后的值var2,其中CAS是为了保证在多线程情况下,不会产生两个相同的随机值,即确保随机性。
next( )方法
其实上述常用的产生随机数的方法,都有一个共同的特点,它们都无一例外地调用了next( )方法。
protected int next(int var1) {
AtomicLong var6 = this.seed;
long var2;
long var4;
do {
var2 = var6.get();
// 关键
var4 = var2 * 25214903917L + 11L & 281474976710655L;
} while(!var6.compareAndSet(var2, var4));
return (int)(var4 >>> 48 - var1);
}
实际上,该代码就是一个数学公式:
var4 = var2 * 25214903917L + 11L & 281474976710655L;
这个公式,称作线性同余随机数生成器(建议去看看《计算机程序设计艺术》,详细了解一下…)。
那,
总而言之,言而总之,随机数的基本原理就是,以所得随机数种子为基准,按照特定的数学公式,依次生成随机数序列。
或者说,随机数基于一个种子,种子固定,随机数序列就固定。
本人实在才疏学浅,本文就结束啦。如有错误,劳烦联系我哦。