• 随机数介绍

在程序开发中,我们经常会用到随机数。譬如数字签名、数据加密、以及一些取样的场景下。但需要注意的是,许多开发语言API 所提供的随机函数并非真正意义上的随机,而是伪随机,至于原因,本文最后会进行解释。

  • jdk中Random类

jdk中提供了Random类供我们使用

	        // within int range
		System.out.println(new Random().nextInt());

		// [0,23)
		System.out.println(new Random().nextInt(23));

		// [0.0,1.0)
		System.out.println(new Random().nextDouble());

		// [0.0,1.0)
		System.out.println(new Random().nextDouble());

		// generate true or false with equal probability
		System.out.println(new Random().nextBoolean());
  • 种子

值得注意的是Random类中提供了另外一个带参构造函数 Random(long seed)

我们输入下面代码

Random random1 = new Random(23);
for (int i = 0; i < 5; i++) {
System.out.println(random1.nextInt());
}
Random random2 = new Random(23);
for (int i = 0; i < 5; i++) {
System.out.println(random2.nextInt());
}
某次结果如下

-1150482841
1434614297
156591366
825130495
960144037
-------------
-1150482841
1434614297
156591366
825130495
960144037

有读者可能感到困惑,为什么两个Random 独立生成出的数字序列完全相同,对,不必疑惑!即使生成10000次,他们生成的随机数序列也完全相同!    这也是本文开头所说的伪随机。

大部分的开发语言中,随机数产生都采用了线性同余法

数学表达式如下

X(n+1) = (a * X(n) + c) % m

所以,随机数值是其实是通过公式算出来的。当你在构造不同的Random类时传入了相同的种子(Seed)时候,必然会得到相同的数字序列!

我们观察下jdk产生随机数的核心函数:

     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));
    }

其中(oldseed * multiplier + addend) & mask就是采用了线性同余法。只不过jdk开发者进行了一些处理而已。 而我们使用无参构造函数时,会默认使用时间作为种子,所以,即使定义了若干Random 类,他们产生的随机数序列也不会相同。 好了,今天学习就到这里,下次我们讲讲 Apache Commons Lang 中的 RandomUtils类