Java Random类源码分析

前言

每次特别好奇计算机如何产生随机数的,他这种完全按照规律执行的代码,如何才能产生随机数呢?这里我给出大家一个解答——答案是不可以。计算机产生的伪随机数。即给出一个种子,给出一些复杂的算法。把他变大之后,就产生了类似随机数的序列。

正文

下面我们来看一下代码

        Random random = new Random(50);
        System.out.println(random.nextInt(100));
        System.out.println(random.nextInt(100));
        Random random1 = new Random(50);
        System.out.println(random1.nextInt(100));
        System.out.println(random1.nextInt(100));

运行结果是:

17
88
17
88

看呆了吧,这玩意完全就类似一个数组,如果你不信,你可以多输出几次,下面我们来看下源码。分析下到底他是一个什么样子的数组。并且如何才能产生我们需要的“伪随机数”。
首先观察构造函数,其实就时找到一个seed,也许你会看到比较复杂的东西,我们先看比较简单的有参构造函数

public Random(long seed) {
        if (getClass() == Random.class)
            this.seed = new AtomicLong(initialScramble(seed));
        else {
            // subclass might have overriden setSeed
            this.seed = new AtomicLong();
            setSeed(seed);
        }
    }

这里本来我不想贴代码的。可是我发现无参构造函数比较复杂。我就还是研究下吧,注释很容易明白,一切的一切都是为了继承。其实我们只走if(true)流程。我们看下initialScramble(seed)

    private static final long mask = (1L << 48) - 1;
    private static final long multiplier = 0x5DEECE66DL;
    private static long initialScramble(long seed) {
        return (seed ^ multiplier) & mask;
    }

这里估计大部分人会迷茫,这他妹的是什么。说实在的我也不懂。multiplier 这个数据为啥是这个。有撒用,貌似一个最合理的解释的是吧数字变大,可以让种子变得更加貌似随机一点。至于mask,貌似要去掉正负标志位,其实没啥用,继续来研究另外一个构造函数。

    public Random() {
        this(seedUniquifier() ^ System.nanoTime());
    }
    private static long seedUniquifier() {
        // L'Ecuyer, "Tables of Linear Congruential Generators of
        // Different Sizes and Good Lattice Structure", 1999
        for (;;) {
            long current = seedUniquifier.get();
            long next = current * 181783497276652981L;
            if (seedUniquifier.compareAndSet(current, next))
                return next;
        }
    }

seedUniquifier() 这个函数我不懂,网上搜到说是多线程。大家不要介意。只要知道这个数字貌似比较大,并且跟当前时间xor后更显得随机,好了开始我们真正的随机数的产生。

    public boolean nextBoolean() {
        return next(1) != 0;
    }

这个最简单。我们会发现出现一个next函数,看下next函数。

    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 = a* oldseed + b ;
这个东西。我们在getBool 中传递参数是1.我们很容易发现我们只保留了一位二进制数字。当然只能取两种结果。
好了我们来看一个复杂的东东

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

第四行r为一个无符号的int型’随机数’。if内部解释很清楚,直接看else。整理后的代码是这样,其实就是一直取余数,最终得带的余数小鱼边界就好了。不在详细研究。
至于小数的形式。这里不在研究,也比较简单。
下面说下如果想产生我们认为的足够随机的随机数。其实很简单。聪明的我们发现调用无参构造函数就好了嘛!的确,这里我就不再给大家写代码。另外还有一个java.math.random() 类对这个类进行了封装我们可以调用Math.random() .关于线程我这里给不出一个合理的答案。至于我这个写Android的应用的一般不会出现这种问题。这里就不再深入研究了。以后有机会再补充。

后记

总算理解了大概这个玩意。这里总算明白这个问题了,总算不是完全不直到到底随机数如何产生的。希望对大家有帮助

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值