Java Random类深入分析

在过去的编程中,经常会用到java.util.Random类,当时就对Random类的实现非常好奇;今日在看《Effective Java》第47条时,看到作者的建议和书中提到的Random类的错误使用,于是决定阅读Random类的实现方法一探究竟。

Random实例通过线性同余算法(linear congruential algorithm)产生伪随机数,该算法使用了一个48位的种子(seed)。种子通过Random类的setSeed方法初始化,其源代码如下:

synchronized public void setSeed(long seed) {
103         seed = (seed ^ multiplier) & mask;
104         this.seed.set(seed);
105         haveNextNextGaussian = false;
106     }

当创建一个新的伪随机数生成器时,构造器都会调用setSeed方法初始化种子,其中带有参数的构造器会直接使用其参数调用setSeed,源代码如下:

078     public Random(long seed) {
079         this.seed = new AtomicLong(0L);
080         setSeed(seed);
081     }

而对于无参构造器来说,他会使用一个与当前时间相关的变量作为参数调用Random(seed)方法,源代码如下:
062     public Random() { this(++seedUniquifier + System.nanoTime()); }
063     private static volatile long seedUniquifier = 8682522807148012L;



通过上诉任意一种方式(Random()、Random(longNum)、setSeed(longNum))初始化种子之后,我们就可以调用next(bits)、netInt()、netFloat()等方法获取伪随机数了。接下来我将结合源代码对各个方法进行分析:

1. next(bits)方法:

protected int next(int bits)方法截取48位种子的前bits位作为产生的伪随机数返回,该方法是产生一系列随机数的核心方法,其他产生随机数的方法next**()均需依赖于该方法,其源代码如下:

133     protected int next(int bits) {
134         long oldseed, nextseed;
135         AtomicLong seed = this.seed;
136         do {
137         oldseed = seed.get();
138         nextseed = (oldseed * multiplier + addend) & mask;
139         while (!seed.compareAndSet(oldseed, nextseed));
140         return (int)(nextseed >>> (48 - bits));
141     }

2. nextInt()方法:

该方法通过调用next(32)实现,返回的整型值正负皆有可能

3. nextInt(int n)方法:

该方法用于返回一个0(包括)~n(不包括)的伪随机数,当非整数是,抛出异常;当n为2的m次幂时,返回48位种子的前n位作为生成的伪随机数;当n不是2的次幂时,采用如下源代码所示方法获取伪随机数,代码中的while循环是为了使用于生成val的bits能够仅仅存在于0~2^31-n的范围内,而不会超过2^31-n;因为当n不是2的次幂时,如果bits采用大于了2^31-n值,就会导致产生的val在0~n之间分布的概率不均衡。那么该方法为什么要对2的次幂做特殊处理呢?因为对于next()所采用的线性同余算法,其部分低位会有一个短周期,而求余的结果就是获取低位,所以需要采用获取高位的方法做特殊处理。

248     public int nextInt(int n) {
249         if (n <= 0)
250             throw new IllegalArgumentException("n must be positive");
251  
252         if ((n & -n) == n)  // i.e., n is a power of 2
253             return (int)((n * (long)next(31)) >> 31);
254  
255         int bits, val;
256         do {
257             bits = next(31);
258             val = bits % n;
259         while (bits - val + (n-1) < 0);
260         return val;
261     }

4. nextLong()方法:

该方法通过连接两个48位种子的前32位来获得long型伪随机数

282     public long nextLong() {
283         // it's okay that the bottom word remains signed.
284         return ((long)(next(32)) << 32) + next(32);
285     }

5. nextBytes(byte[] bytes)方法:

该方法多次调用next(32)获取32位伪随机数,并将获取的伪随机数分成4个8位byte数据,然后从低到高想bytes数组中填充

源代码如下:
162     public void nextBytes(byte[] bytes) {
163     for (int i = 0, len = bytes.length; i < len; )
164         for (int rnd = nextInt(),
165              n = Math.min(len - i, Integer.SIZE/Byte.SIZE);
166          n-- > 0; rnd >>= Byte.SIZE)
167         bytes[i++] = (byte)rnd;
168     }

6. nextBoolean()方法

该方法将48位种子的第48位与0比较,如不等于0,则返回1;如等于0,则返回0;源代码如下:

307     public boolean nextBoolean() {
308     return next(1) != 0;
309     }



7. nextFloat()方法

该方法用于产生一个范围在0(包括)~1(不包括)之间的单精度浮点数,该方法的源代码如下:

350     public float nextFloat() {
351         return next(24) / ((float)(1 << 24));
352     }

为什么next的参数是24呢?因为float型数据的尾数有23位,加上个位的1,所以float数据的有效数字总共是24位(二进制),为了保证精度和使产生的伪随机数能够分布均匀,所以next的参数是24.

8. nextDouble()方法

原理同上,源代码如下:

393     public double nextDouble() {
394         return (((long)(next(26)) << 27) + next(27))
395         / (double)(1L << 53);
396     }

你可能感兴趣的:(Java Random类深入分析)