3

阅读关于 Fisher-Yates 的维基百科页面,将最后一段改组是:

最后,需要注意的是,即使生成完美的随机数,也可能由于生成器的不当使用而在实现中引入缺陷。例如,假设 Java 实现为每次调用 shuffler 创建一个新的生成器,而不传递构造函数参数。然后生成器将由语言的时间默认播种(在 Java 的情况下为 System.currentTimeMillis())。因此,如果两个调用者在小于时钟粒度(Java 情况下为一毫秒)的时间跨度内调用混洗器,他们创建的生成器将是相同的,并且(对于相同长度的数组)相同的排列将被生成。如果 shuffler 被快速连续调用多次,这几乎肯定会发生,在这种情况下会导致极其不均匀的分布;它也可以应用于来自不同线程的独立调用。更健壮的 Java 实现将使用在 shuffler 函数之外定义的生成器的单个静态实例。

除了本段的最后一句话,我什么都理解。当作者说:

更健壮的 Java 实现将使用在 shuffler 函数之外定义的生成器的单个静态实例。

4

4 回答 4

12

这意味着您有一个像这样的静态函数:

class RandomUtil {

    public static final Random rand = new Random();     

}

然后你使用这个 rand 生成器RandomUtil.rand.nextInt()来生成你所有的随机数。您只使用这一个实例并确保不会出现 wiki 中提到的问题。

于 2012-07-04T06:10:29.590 回答
4

那篇文章的一部分是错误的:

例如,假设 Java 实现为每次调用 shuffler 创建一个新的生成器,而不传递构造函数参数。然后生成器将由语言的时间默认播种(在 Java 的情况下为 System.currentTimeMillis())。因此,如果两个调用者在小于时钟粒度(Java 情况下为一毫秒)的时间跨度内调用混洗器,他们创建的生成器将是相同的,并且(对于相同长度的数组)相同的排列将被生成。

这实际上不是真的。尽管为每个调用创建一个新的生成器是不好的做法,但Random该类包含专门用于防御这种情况的代码:

 public Random() {
     this(++seedUniquifier + System.nanoTime());
 }
 private static volatile long seedUniquifier = 8682522807148012L;

首先,分辨率是一纳秒,而不是一毫秒。创建生成器并使用它一次通常需要超过一纳秒的时间。其次,即使没有,seedUniquifier每次调用的值也会不同。

因此,虽然在每次调用时生成一个新的仍然是一个坏主意Random,但它的危害比那篇文章所暗示的要小。

于 2012-07-04T08:11:41.787 回答
2

使用相同种子和相同排列的两个随机数生成器可能比使用一个生成器生成不同的随机数产生更少的随机性。

假设您同时创建了两个默认随机数生成器。他们使用时间戳(以毫秒为单位)作为种子。并且,将具有相同的排列。现在,您尝试从两个不同的生成器中获取第一个随机数,它们很可能是相同的。

为避免出现这种情况,请创建一个public static final Random变量并在任何地方使用它。

于 2012-07-04T06:11:01.333 回答
0

为了使这个实现健壮并且不可能生成可预测的随机数,生成器类应该在 shuffler 类之外的静态方法中定义。因此,在任何给定时间都不会创建生成器的两个或更多对象。因此最大限度地减少了生成相同随机数的可能性。

于 2012-07-04T06:15:00.103 回答