天天看点

数字47引发的思考 多少人错误的理解了Random随机数

在看Java编程思想的时候注意到作者总是使用数字47来作为Random初始化的种子,然后在网上看到很多人胡乱分析一通,好多人都对作者的那句话“数字47在我加盟的一家学院里被认为是“魔幻数字”,至今仍是这样”理解错了,这句话只是说明了作者为什么选择这个数字,并没有说这个47确实有魔力。

首先贴几个网上普遍的错误理解:

1.下面这个应该是不知道Random有一个无参构造器,另外以47为种子的random对象调用nextInt(26)这个方法只有第一次产生的随机数是24,调用第二次就是13。

数字47引发的思考 多少人错误的理解了Random随机数

                                                                         图 1 - 1 网友的错误理解

数字47引发的思考 多少人错误的理解了Random随机数

                                                          图 1 - 2 运行十次的结果

2.下面这个说的优点玄乎,应该是没理解作者的话:“the number 47 was considered a "magic number" at a college I attended, and it stuck.”。翻译过来是:数字47在我加盟的一家学院里被认为是“魔幻数字”,至今仍是这样。注意这句话跟Java随机数根本没有任何关系,只是作者的个人意愿选的47作为种子,这样我们写的程序使用和作者一样的数字作为种子的时候结果会相同,方便查错,这里的47换成其他数字也是可以的。假如作者使用56作为种子,你照抄作者的程序也是用56作为种子你俩结果也会一样。

数字47引发的思考 多少人错误的理解了Random随机数

下面说明下对Random正确的理解:

01.Random类包含两个构造方法, public Random() 和 public Random(long seed),如下

//该构造方法使用一个和当前系统时间对应的相对时间有关的数字作为种子数,
//然后使用这个种子数传入到含参构造器来构造Random对象。
public Random() {
        this(seedUniquifier() ^ System.nanoTime());
    }
//含参构造器
public Random(long seed) {
    if (getClass() == Random.class)
        this.seed = new AtomicLong(initialScramble(seed));
    else {
        this.seed = new AtomicLong();
        setSeed(seed);
    }
}           

02.当我们调用无参构造器创建Random对象的时候,JVM会为我们指定一个与当前系统时间有关(参考网上关于System.nanoTime()的方法介绍)的种子来构造Random对象。

03.相同种子的Random对象( random1 和 random2 ),相同次数(第一次和第一次,第二次和第二次等等等)生成的随机数字是完全相同的,如下

Random random1=new Random(56);
        Random random2=new Random(56);
        for(int i=0;i<10;i++)
        {
            int ran0=random1.nextInt(32);
            int ran1=random2.nextInt(32);
            System.out.print("第"+i+"次:"+ran0);
            System.out.println("   "+ran1);
        }           

输出结果:这个程序无论执行多少次结果都是这样的。

数字47引发的思考 多少人错误的理解了Random随机数

04.不同种子的Random对象( random3 和 random4 ),相同次数生成的随机数字是完全不相同的,如下:

Random random3=new Random(56);
        Random random4=new Random(1578645);
        for(int i=0;i<10;i++)
        {
            int ran0=random3.nextInt(32);
            int ran1=random4.nextInt(32);
            System.out.print("第"+i+"次:"+ran0);
            System.out.println("   "+ran1);
        }           

执行结果:random3和random4相同次数生成的数字完全不同,因为他们的种子不同。

数字47引发的思考 多少人错误的理解了Random随机数

05.当每次调用无参构造器创建的Random对象生成的随机数都不同,因为调用无参构造器会使用系统时间有关的数字作为种子,每次时间不一样种子也就不一样。如下:

Random random1=new Random();
        for(int i=0;i<10;i++)
        {
            int ran0=random1.nextInt(32);
            System.out.println("第"+i+"次:"+ran0);
        }           

连续执行两次的结果对比:

数字47引发的思考 多少人错误的理解了Random随机数

06.nextInt(int x) 方法只表示随机生成一个在0到x(包含0但不包含x)范围之间的整数,此处的参数x与种子无关,其他类型类推。

07.总结:种子只是随机算法的起源数字,和生成的随机数字的区间无关。相同种子数的Random对象,相同次数(第一次与第一次)生成的随机数字是完全相同的,不同次数(第一次与第N次)生成的随机数字是不同的。没有种子会以jvm时间相关的数字作为种子,所以不同时间生成的随机数字是不相同的。

08.强调一下,所有随机算法生成的数都是伪随机数,靠算法只能实现伪随机,因为算法是写死的。真正意义上的随机需要物理硬件协助,举个例子就是抛硬币。