伪随机
计算机世界是无数个0和1组成的,非黑即白。不存在不确定的数,所以计算机压根就无法生成真正的(不确定的)随机数。
但是实际场景又需要一个随机数,这时候就开始想办法,常是利用计算机抓取一些数值,然后将这些数值输入至一个复杂算法当中,通过一系列运算得出一个数字,这就是平常说的赝随机数了。
这个抓取的数值,叫做种子
因为这个是通过算法得到的随机数,所以输入一个特定的值,必然得到一个特定的结果,要是通过算法逆推回去,就暴露了种子值,这样就可以复现,能复现的话,就太恐怖了,就比如在赌场就可以疯狂赢钱了。所以就需要让这个输入的值,无法复现,所以这个种子怎么选就很关键,
最常用的是把时间去当做种子,因为时间一直在变,当然,还有更高级的 一些硬件随机数卡,靠的是根据天线收集环境中的电磁波噪音。 还有一些硬件甚至依靠检测内部的放射性元素衰变计数来产生随机数。
伪随机在游戏中又不能这样随机,例如LOL中的暴击率50%,那么我不可能让运气王刀刀暴击,因为这样对手的体验非常差,所以就添加了更多的限制条件,第一刀没暴击,那么第二刀的概率就会更大一点。
还有在听歌的时候,就需要根据用户的需求去随机,在晚上的时候随机更安静的音乐,随机用户更喜欢的音乐,随机过的音乐不能再次出现,一般常用的算法,洗牌算法。
下面介绍一下java中的random的实现原理:(引用)
在java当中有时我们会常用Random来生成任意的随机数,以来满足某些需求。但是可能我们并不知道random内部的实现机制,今天将同大家一起深入研究random的源码。
Random类本身有两个构造函数,一个带参的一个不带参的。
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
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);
}
}
可以看到不带参的构造函数内部调用了带参的构造。传入的参数为seeduniquifier方法生成的seed和获取到的当前原子时钟的当前时间的与操作后的值,
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;
}
}
方法里面是一个for循环,但是没有任何循环条件,也就意味着和while(true)差不多,是一个无限循环的条件,循环中结束的条件是seedUniquifier的值和seedUniquifier.get()后获得的值是一样的,然后把current和一个常量值的乘积赋值给seedUniquifier,很明显可以看到,每次循环后seedUniquifier的值都会变成一个新的next,而且终止循环,把新的值返回。原子时钟获取到的当前时间是一直变大的,所以无参构造有参传入的参数一定是每次都不一样的。
看完无参可以看下有参的,进入方法体里面发现里面进行了逻辑判断,getClass()和Random.class进行比对,那么首先进行比对的原因是为了防止子类自己重写了setSeed方法,如果针对常量的话,则是直接比较值的大小,而对于对象则是比较对象的地址是否相同,equals不能用于基本类型的比较,其实equals内部也是用比对的。AtomicLong是原子long,根据java中原子的操作性可知,不能同时操作它。
原文链接:https://blog.csdn.net/wangluotianxi/article/details/79670680