在过去的编程中,经常会用到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 | } |