我真正的问题是选项 1 在数学上是否有效。
让我们从选项 2 开始。使用的随机数生成器java.util.Random
在 javadoc 中指定如下:
该类使用 48 位种子,该种子使用线性同余公式进行修改。(参见 Donald Knuth,计算机编程的艺术,第 2 卷,第 3.2.1 节。)
并且在各种方法的 javadocs 中有更具体的细节。
但关键是我们使用的是由线性同余公式生成的序列,并且这些公式具有显着程度的自相关......这可能是有问题的。
现在使用选项 1,您Random
每次都使用具有新种子的不同实例,并应用一轮 LC 公式。因此,您将获得一系列可能与种子自相关的数字。但是,种子的生成方式不同,具体取决于 Java 版本。
Java 6 这样做:
public Random() { this(++seedUniquifier + System.nanoTime()); }
private static volatile long seedUniquifier = 8682522807148012L;
...这根本不是很随机。如果您Random
以恒定间隔创建实例,则种子可能间隔很近,因此您的选项 #1 生成的随机数序列可能是自相关的。
相比之下,Java 7 和 8 这样做:
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);
上述产生的种子序列可能是(真实)随机性的更好近似。这可能使您的选项#1 优于选项#2。
Java 6 到 8 中选项 #1 的缺点是System.nanoTime()
可能的调用涉及系统调用。那是相对昂贵的。
所以简短的回答是,从数学的角度来看,选项#1 和选项#2 中的哪一个产生更好质量的“随机”数字是 Java 版本特定的。
在这两种情况下,数字的分布在足够大的样本量上都是均匀的,尽管我不确定在过程是确定性的情况下讨论概率分布是否有意义。
然而,这两种方法都不适合作为“加密强度”随机数生成器。