前言
每次特别好奇计算机如何产生随机数的,他这种完全按照规律执行的代码,如何才能产生随机数呢?这里我给出大家一个解答——答案是不可以。计算机产生的伪随机数。即给出一个种子,给出一些复杂的算法。把他变大之后,就产生了类似随机数的序列。
正文
下面我们来看一下代码
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的应用的一般不会出现这种问题。这里就不再深入研究了。以后有机会再补充。
后记
总算理解了大概这个玩意。这里总算明白这个问题了,总算不是完全不直到到底随机数如何产生的。希望对大家有帮助