Random
1.线性同余实现伪随机
程序员对随机数一般都不陌生,而且众所周知,计算机中通常实现的是伪随机数列。何为伪随机数列?
伪随机数(或称伪乱数),是使用一个确定性的算法计算出来的似乎是随机的数序,因此伪随机数实际上并不随机。
既然是通过算法来模拟随机过程,那什么样的算法可以达到接近随机的效果?
比较简单的一种便是线性同余法:
其中 A 称为乘数,B 称为增量,M 称为模数,当 A=0,C≠0 时称为和同余法,当 C=0,A≠0 时称为乘同余法,A≠0,C≠0 时,称为混合同余法。乘数、增量和模数的选取可以多种多样,只要保证产生的随机数有较好的均匀性和随机性即可,一般采用 m=2km=2k 混合同余法。
用线性同余法产生随机数的特点是非常容易实现,生成速度快,但是弊端也很明显,32位的数周期最长只能到2的32次方,达不到需要高质量随机数的应用如加密应用的要求
2.JDK中伪随机数生成
很多平台都采用以上线性同余发生器实现了伪随机数的生成机制,比如下面是常见的平台实现中对应的参数(from wiki):
代码演示如下:
public class LinearCongruentialGenerator {
final static int mask = (1 << 31) - 1;
static IntStream randBSD(int seed) {
return iterate(seed, s -> (s * 1_103_515_245 + 12_345) & mask).skip(1);
}
static IntStream randMS(int seed) {
return iterate(seed, s -> (s * 214_013 + 2_531_011) & mask).skip(1)
.map(i -> i >> 16);
}
public static void main(String[] args) {
System.out.println("BSD:");
randBSD(0).limit(10).forEach(System.out::println);
System.out.println("\nMS:");
randMS(0).limit(10).forEach(System.out::println);
}
}
java 中 java.util.Random类的实现中,发生器的关键代码如下:
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
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));//丢弃低比特位,保留高比特位
}
public int nextInt() {
return next(32);
}
public long nextLong() {
// it's okay that the bottom word remains signed.
return ((long)(next(32)) << 32) + next(32);
}
同时可以看到,上面实现中特意对比特位进行了截取,丢弃低比特位,保留高比特位。
3.Random类
Random简介
在 JDK 的Random 类中,官方文档有如下介绍和建议:
- 该类的一个实例用于生成伪随机数流。 类使用一个 48 位种子,该种子使用一个线性同余公式进行修改。
(参见Donald Knuth,《计算机编程的艺术》,第二卷,第3.2.1节。) - 如果使用相同的种子创建了两个 Random 实例,并且对每个实例进行了相同的方法调用序列,则它们将生成并返回相同的数字序列。为了保证此属性,为 Random 类指定了特定的算法。为了实现 Java 代码的绝对可移植性,Java实现必须将此处显示的所有算法用于 Random 类。但是,Random 类的子类允许使用其他算法,只要它们遵守所有方法的常规协定即可。
- 由 Random 类实现的算法使用
protected int next(int bits)
作为核心实用方法,该方法在每次调用时最多可以提供32位的伪随机生成数。 - java.util.Random的实例是线程安全的。但是,跨线程并发使用同一 java.util.Random实例可能会引起争用并因此导致性能下降。多线程设计中建议使用{java.util.concurrent.ThreadLocalRandom}。
- java.util.Random 的实例不是加密安全的。考虑改为使用 java.security.SecureRandom 来获取加密安全的伪随机数生成器,以供对安全敏感的应用程序使用。
Random构造方法
Random 类有两个构造方法:
Random()
Random(long seed)
先来说一下 Random(long seed)
,源码如下(便于理解,翻译了注释):
/**
* 使用单个 long类型的种子创建一个新的随机数生成器。
* seed是伪随机数生成器的内部状态的初始值,由 next方法维护。
* 此方法等价于:
* Random rnd = new Random();
* rnd.setSeed(seed);
*
* @param seed 最初的种子
*/
public Random(long seed) {
/*
* 如果使用的是 Random 本类, 则调用 initialScramble方法
* 对 seed 进行初始混乱(可以看出,采用的是混合同余法)
*/
if (getClass() == Random.class)
this.seed = new AtomicLong(initialScramble(seed));
else {
/*
* 否则,则是 Random 的子类,子类有可能会重写setSeed方法,
* 因此,调用 setSeed 方法对 seed 进行操作,而不是调用本类
* 的混乱方法。
*/
this.seed = new AtomicLong();
setSeed(seed);
}
}
这种重载形式的构造方法,由用户提供种子(seed),但一般不建议这么做,因为种子会影响随机分布的质量和区域。而且指的注意的事,如果使用相同的种子创建了两个 Random 实例,并且对每个实例进行了相同的方法调用序列,则它们将生成并返回相同的数字序列。
用代码炒个栗子:
Random r1 = new Random(996);
Random r2 = new Random(996);
for (int i = 0; i < 6; i++) {
System.out.println(r1.nextInt() == r2.nextInt());
}
结果全为 true
,即如果使用相同的种子创建了两个 Random 实例,两个实例同时调用第N次且调用的是相同方法,则生成的结果相同。
再来看一下 Random()
,源码如下:
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
可以看到,其内部还是调用了 Random(long seed)
的重载形式,只不过这时的 种子(seed) 不是由用户提供,而是经过计算生成,源码如下:
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;
}
}
private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
此时 seed 是由 Random类自行维护,其 seed 的初始值为 8682522807148012L * 181783497276652981L 再异或当前系统纳秒时间。因此几乎不可能生成两个 seed 值相同的 Random 实例,也就几乎不会出现生成相同结果的情况(除非巧合)。
Random的常用方法
方法 | 介绍 |
void setSeed(long seed) | 设置 seed(种子) |
int nextInt() | 随机生成一个 int 值,范围:[Integer.MIN_VALUE,Integer.MAX_VALUE] |
int nextInt(int bound) | 随机生成一个 int 值,范围:[0,bound-1] |
long nextLong() | 随机生成一个 long 值,范围:[Long.MIN_VALUE,Long.MAX_VALUE] |
float nextFloat() | 随机生成一个 float 值,范围:[0,1] |
double nextDouble() | 随机生成一个 double 值,范围:[0,1] |
double nextGaussian() | 正态分布生成一个 double 值的伪随机数,平均值为 0.0,生成器序列标准差为1.0 |
void nextBytes(byte[] bytes) | 随机生成 bytes.length 个 byte 值,并填充到 bytes 数组中,每个值的范围:[Byte.MIN_VALUE,Byte.MAX_VALUE] |
boolean nextBoolean() | 随机生成一个 boolean 值,true | false |