Java 随机数

问:下面程序的输出语句是什么?为什么?
public class Test {
    private static Random rnd = new Random();

    public static void main(String[] args) {
        StringBuffer word = null;
        switch(rnd.nextInt(3)) {
            case 1:
                word = new StringBuffer('M');
                break;
            case 2:
                word = new StringBuffer('V');
                break;
            default:
                word = new StringBuffer('P');
                break;
        }
        word.append('a');
        word.append('b');
        word.append('c');
        System.out.println(word);
    }
}

答:这是一道虽然很简单,但是比较坑的题目。无论运行多少次输出结果都是 abc。

因为很多人一上来思考点首先被随机函数吸引,殊不知是个陷阱,被掉进去的是 StringBuffer 构造方法的参数,StringBuffer 的构造方法如下:

//无参构造方法
public StringBuffer()
//指定容量的字符串缓冲区对象
public StringBuffer(int capacity)
//指定字符串内容的字符串缓冲区对象 
public StringBuffer(String str)

所以很多人以为上面调用 StringBuffer('X') 是调用的 StringBuffer(String) 构造方法,殊不知传入的是字符类型,不是字符串类型,所以字符会被转为 int 使用,故而调用了 StringBuffer(int) 构造方法,所以默认是 "",然后 append 了 "abc" 而已,真是没想到。

问:下面程序多次输出结果相同吗?为什么?
public class Test {
    public static long createRandom() {
        Random random = new Random(1000);
        return random.nextInt();
    }

    public static void main(String[] args) {
        for (int i=0; i<10; i++) {
            System.out.println("index["+i+"]="+createRandom());
        }
    }
}

答:相同,10 次运行随机值都是同一个。

因为这里使用存在一个误区,如果上面 for 循环里 random 实例为同一个则会产生不同的随机数,而这里由于在构造方法指定了固定初始 seed 值为 1000,而 Random 类每次运算伪随机值实质都是通过这个 seed 成员变量取旧值进行规律运算得到下一个新值,由于我们这里构造方法指定了首次 seed 为 1000,然后每次调用 createRandom 函数都新建了 Random 实例,所以相当于每次调用 random 的 nextInt 方法都是拿的初始值运算的下一个随机值,除非对同一个 random 多次调用 nextInt 才会拿到不同值。

此时有人可能有疑问,那构造 Random 时使用无参的 Random() 构造呢?这样就不会相同了,因为 Random 的无参构造方法生成初始 seed 值会拿当前系统时间戳参与运算,所以每次都会不同,而指定 seed 的构造方法就不会拿变化的因素参与初始 seed 运算,而是直接用我们设置的 seed 参数参与运算,所以才有了上面的结果。

如上解释直接可以参考如下 Random 源码核心部分理解即可:

public class Random implements java.io.Serializable {
    ......
    private final AtomicLong seed;

    private static final long multiplier = 0x5DEECE66DL;
    private static final long addend = 0xBL;
    private static final long mask = (1L << 48) - 1;

    private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)

    // IllegalArgumentException messages
    static final String BadBound = "bound must be positive";
    static final String BadRange = "bound must be greater than origin";
    static final String BadSize  = "size must be non-negative";

    //依据时间戳产生的伪随机seed。
    public Random() {
        this(seedUniquifier() ^ System.nanoTime());
    }

    ......

    private static final AtomicLong seedUniquifier
        = new AtomicLong(8682522807148012L);

    /**
     * 我们需要关注的构造方法
     */
    public Random(long seed) {
        //我们调用走进 if 分支
        if (getClass() == Random.class)
            this.seed = new AtomicLong(initialScramble(seed));
        else {
            // subclass might have overriden setSeed
            this.seed = new AtomicLong();
            setSeed(seed);
        }
    }

    ......
    synchronized public void setSeed(long seed) {
        this.seed.set(initialScramble(seed));
        haveNextNextGaussian = false;
    }

    ......
    //调用next获取随机值
    public int nextInt() {
        return next(32);
    }

    //进行一些按位随机获取,实质调用next方法产生随机值
    public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);

        int r = next(31);
        int m = bound - 1;
        if ((bound & m) == 0)  // i.e., bound is a power of 2
            r = (int)((bound * (long)r) >> 31);
        else {
            for (int u = r;
                 u - (r = u % bound) + m < 0;
                 u = next(31))
                ;
        }
        return r;
    }
    ......


    /**
     * 通过 this.seed 成员获取 oldseed 旧随机值。
     * 通过固定参数算法与旧随机seed生成新的随机值,然后通过 CAS 并发安全操作更新。
     */
    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));
    }
    ......
}

本文参考自 随机数相关笔试题踩坑解析

你可能感兴趣的:(Java 随机数)