1

我有一个 java 方法,它必须在很短的时间内生成大量随机数。我的第一种方法是使用 Math.random (它的工作速度非常快),但我有一个假设,因为我Math.random在另一个后面这么快调用,“随机”并不是真正随机的(或不太随机),因此(但我需要它尽可能随机)。

我现在有两个问题:

  1. 我的假设是否正确,因为在很短的时间内调用的数量会减少随机输出的随机性?如果 1. 的答案是肯定的:
  2. 消除随机性较小的问题的最快方法(每次调用)是什么?

我已经玩过SecureRandom,但它至少比普通的 Math.random 慢 15 倍,这对于我的要求来说太慢了。

4

7 回答 7

4

TL;DR:你的假设是错误的。

Math.random作用于单个实例java.util.Random

返回一个带正号的双精度值,大于或等于 0.0 且小于 1.0。返回值是伪随机选择的,从该范围(近似)均匀分布。

当这个方法第一次被调用时,它会创建一个新的伪随机数生成器,就像通过表达式一样

new java.util.Random()

来自JavaDoc

现在,java.util.Random使用一个线性同余公式,该公式以“很可能与此构造函数的任何其他调用不同”的数字为种子。1

由于这是一个伪随机进程——即它会从同一个种子中给出完全相同的值——你从中提取数字的速度Math.random对它们的随机性没有影响。

于 2015-09-22T19:28:53.847 回答
3

使用 Random 类的随机数使用一种算法,该算法可以对 int 进行位破坏,从而为您提供新的 int。无论您调用它的速度或次数,它都会使用相同的算法。进阶就是进阶。

要对此进行测试,请为其播种一个数字,例如 42。然后观察进程。再次用相同的号码播种。完全相同的进展。

这种方法的缺点是数字不是真正随机的。它们非常随机,对于大多数事情来说已经足够了,但不是完全随机的。

我通过顽固的测试电池运行了 Random 方法的输出。它以优异的成绩通过了其中的大多数,一个是临界的,一个是完全失败的。这就是我们所说的随机性。

另外,因为它使用日期时间戳来播种自己,所以在某些情况下它是可以预测的。想象某人每周一早上启动并运行您的任务,这是该周的第一件事。有一些可预测性,因为它将以星期一早上 8 点到 8:30 之间的时间戳运行。

因此,对于大多数与安全性无关的操作,Random 已经足够好了。甚至很多。

另一方面,SecureRandom 将生成真正的随机数。它通过查看系统时序和其他基于无数因素而从秒到秒变化的事物来做到这一点。

缺点是这些因素在一秒钟内变化的频率非常高,因此 SecureRandom 在一段时间内只能生成有限数量的随机数。它确实会尝试提前生成一些并缓存它们以供使用,但是您可以破坏缓存。

这样,就像我的反渗透滤水器一样。它装有一加仑已经过滤过的水。如果你一次性使用一加仑水,那么你会以过滤它的速度得到它——大约每 5 秒 1 盎司或类似的东西。第一加仑很快,然后真的很慢。

于 2015-09-22T19:29:05.977 回答
2

如果你可以使用 Java8,我推荐java.utils.SplitableRandom。它更快并且具有更好的统计分布。在我的测试中,java.utils.SplitableRandom 比 java.utils.Random 快 30 倍。

我用tobijdc answer来写这个答案。

于 2017-07-30T05:14:23.203 回答
0

试试java Kiss库AESPRNG内部生成器。它是线程安全的,在批量请求中使用时大约是 Random 的两倍,并且可以产生 128 位加密强(但可重复,如果您重置种子)伪随机数。它基于 AES CTR 模式,在大多数系统上都进行了高度优化。

kiss.util.AESPRNG prng = new kiss.util.AESPRNG();
double [] x = new double [1_000_000];
prng.nextDoubles(x,0,x.length);

如果您想要一个可重复的序列,请使用 seed(byte[] value16) 或 seed(double value)。重置序列。它是 Random 的直接替代品,但具有许多用于范围或批量数字的便捷方法。它确实比任何建议的替代方案都要好:快速、可重复和 128 位强随机。

于 2016-08-13T06:56:43.583 回答
0
  1. (伪)随机数生成器在给定相同初始种子值的情况下产生相同的结果,而不管调用它的频率如何。它完全是确定性的,与速度无关。种子的选择取决于时间(如果没有明确指定),而不是生成的序列。
  2. 如果您需要更快的速度,您可以预先计算一个大于您所需长度的伪随机数序列的值,然后只需调用一次生成器即可选择序列中的起始位置。这样,您可以在所有后续运行的第一次调用后简单地读出值。您的性能将受到索引和读取保存表的内存的速度的限制。根据您的应用程序,可能不建议重复使用该序列。
于 2015-09-22T19:41:03.023 回答
0

如果您需要快速获取大量数字(如模拟或蒙特卡洛积分),那么加密安全的 RNG 将不够快。java.util.Random()速度很快,但 PRNG 质量很差。您需要的是高质量的快速 PRNG,例如 Mersenne Twister 或 XorShift。例如,看看http://xorshift.di.unimi.it/或我自己的 ojrandlib。

于 2015-09-23T15:54:54.523 回答
0

虽然Random可能已经足够好,但您可以Math.random()通过使用更接近您需要的功能来改进。例如

Random rand = new Random();

for ( loop ) {
   int dice = rand.nextInt(6) + 1;

这比使用快得多,Math.random()但如果你需要一个long

long l = rand.nextLong();

在这种情况下l,有 64 位随机性,但Math.random()最多只有 53 位(实际上它只有 48 位)

于 2015-09-22T19:49:15.813 回答