Java Random源码剖析

Java中,对随机最基本的支持是Math类中的静态方法random(),它生成一个0~1的随机数,类型为double,包括0但不包括1。

System.out.println(Math.random());

可以发现输出结果每次都会不一样。

那么Math.random()是如何实现的呢?我们来看相关代码

private static Random randomNumberGenerator;
   private static synchronized Random initRNG() {
       Random rnd = randomNumberGenerator;
       return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd;
   }
   public static double random() {
       Random rnd = randomNumberGenerator;
       if (rnd == null) rnd = initRNG();
       return rnd.nextDouble();
}

内部使用了一个Random类型的静态变量randomNumberGenerator,调用random()就是调用该变量的nextDouble()方法,这个Random变量只有在第一次使用的时候才创建。

下面我们来仔细看看Random类,位于java.util下。

Random类提供了更为丰富的随机方法,它的方法不是静态方法,使用Random,先要创建一个
Random实例。

Random r = new Random();
System.out.println(r.nextInt());
System.out.println(r.nextInt(100));

nextInt()产生一个随机的int,可能为正数,也可能为负数,nextInt(100)产生一个随机int范围是0~100,包括0不包括100。除了nextInt,还有一些别的方法:

public long nextLong()//随机生成一个long
public boolean nextBoolean() //随机生成一个boolean
public void nextBytes(byte[] bytes)//产生随机字节,字节个数就是bytes的长度
public float nextFloat()//随机浮点数,从0到1,包括0不包括1
public double nextDouble()//随机浮点数,从0到1,包括0不包括1

除了默认构造方法,Random类还有一个构造方法,可以接受一个long类型的种子参数:

public Random(long seed)

种子决定了随机产生的序列,种子相同,产生的随机数序列就是相同的。所以说Random产生的随机数不是真正的随机数,相反,它产生的随机数一般称为伪随机数。真正的随机数比较难以产生,计算机程序中的随机数一般都是伪随机数。
伪随机数都是基于一个种子数的,然后每需要一个随机数,都是对当前种子进行一些数学运算,得到一个数,基于这个数得到需要的随机数和新的种子。数学运算是固定的,所以种子确定后,产生的随机数序列就是确定的,确定的数字序列当然不是真正的随机数,但种子不同,序列就不同,每个序列中数字的分布也都是比较随机和均匀的,所以称之为伪随机数。
Random的默认构造方法中没有传递种子,它会自动生成一个种子,这个种子数是一个真正的随机数,如下所示


   private static final AtomicLong seedUniquifier
       = new AtomicLong(8682522807148012L);
   public Random() {
       this(seedUniquifier() ^ System.nanoTime());
   }
   private static long seedUniquifier() {
       for(;;) {
           long current = seedUniquifier.get();
           long next = current * 181783497276652981L;
           if(seedUniquifier.compareAndSet(current, next))
} 

种子是seedUniquifier()与System.nanoTime()按位异或的结果,System.nanoTime()返回一个更高精度(纳秒)的当前时间,seedUniquifier()返回当前seedUniquifier(current变量)与一个常数181783497276652981L相乘的结果(next变量),然后,设置seedUniquifier的值为next,使用循环和compareAndSet都是为了确保在多线程的环境下不会有两次调用返回相同的值,保证随机性。

有了种子数之后,其他数是怎么生成的呢?我们来看下面的代码:

public int nextInt() {
       return next(32);
   }
   public long nextLong() {
       return ((long)(next(32)) << 32) + next(32);
   }
   public float nextFloat() {
       return next(24) / ((float)(1 << 24));
   }
   public boolean nextBoolean() {
       return next(1) != 0;
   }

它们都调用了next(intbits),生成指定位数的随机数,我们来看下它的代码:

private static final long multiplier = 0x5DEECE66DL;
   private static final long addend = 0xBL;
   private static final long mask = (1L << 48) - 1;
   protected int next(int bits) {
       long oldseed, nextseed;
       AtomicLong seed = this.seed;
       do {
           oldseed = seed.get();
           nextseed = (oldseed * multiplier + addend) & mask;
       } while (!seed.compareAndSet(oldseed, nextseed));
       return (int)(nextseed >>> (48 - bits));
}

简单地说,就是使用了如下公式:nextseed = (oldseed * multiplier + addend) & mask

旧的种子(oldseed)乘以一个数(multiplier),加上一个数addend,然后取低48位作为结果(mask相与)。

这个方法的名称叫线性同余随机数生成器。通过一个递推公式生成一个序列的数字,看起来像是随机分布的数列。该公式的形式为:Xn+1 = (a * Xn + c) mod m。这种生成器的特点是周期性,经过一定数量的迭代之后,生成的序列会开始重复。因此,在使用线性同余随机数生成器时,需要选择合适的参数(a、c、m)来确保生成的随机数序列具有较长的周期性。

总之,Java中随机数基于一个种子,种子固定,随机数序列就固定。而在默认构造方法中,种子是一个真正的随机数。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值