阅读关于费舍尔 - 耶茨洗牌最后一段的维基百科页面是:
最后,需要注意的是,即使生成完美的随机数,也可能通过不正确地使用发生器将缺陷引入实现中。例如,假设Java实现为每次调用shuffler创建一个新生成器,而不传递构造函数参数。然后,生成器将被语言的时间默认播种(在Java的情况下为System.currentTimeMillis())。因此,如果两个调用者在小于时钟粒度的时间跨度内调用shuffler(在Java的情况下为1毫秒),则他们创建的生成器将是相同的,并且(对于相同长度的数组)相同的排列将是生成。如果快速连续多次调用洗牌器,几乎肯定会发生这种情况,在这种情况下会导致极不均匀的分布;它也可以应用于来自不同线程的独立调用。更强大的Java实现将使用在shuffler函数之外定义的生成器的单个静态实例。
我理解除了本段最后一句之外的所有内容。作者在说:
时的意思是什么更强大的Java实现将使用在shuffler函数之外定义的生成器的单个静态实例。?
答案 0 :(得分:11)
这意味着你有一个像这样的静态函数:
class RandomUtil {
public static final Random rand = new Random();
}
然后您使用此兰特生成器RandomUtil.rand.nextInt()
生成所有随机数。您只使用这一个实例,并确保不会出现wiki中提到的问题。
答案 1 :(得分:4)
该文章的一部分是错误的:
例如,假设Java实现为每次调用shuffler创建一个新生成器,而不传递构造函数参数。然后,生成器将被语言的时间默认播种(在Java的情况下为System.currentTimeMillis())。因此,如果两个调用者在小于时钟粒度的时间跨度内调用shuffler(在Java的情况下为1毫秒),则他们创建的生成器将是相同的,并且(对于相同长度的数组)相同的排列将是生成。
事实并非如此。虽然为每个调用创建一个新生成器是不好的做法,但Random
类包含专门用于防御此代码的代码:
public Random() {
this(++seedUniquifier + System.nanoTime());
}
private static volatile long seedUniquifier = 8682522807148012L;
首先,分辨率是一个纳秒,而不是一毫秒。创建生成器并使用它一次通常需要超过一纳秒。其次,即使没有,每次通话的seedUniquifier
值都会有所不同。
因此,虽然在每次通话中生成新的Random
仍然是一个坏主意,但它的危害性略低于该文章的建议。
答案 2 :(得分:2)
使用相同种子和相同排列的两个随机数生成器可能比使用一个生成器生成不同的随机数更少随机性。
假设您已在同一时刻创建了两个默认随机数生成器。他们使用时间戳(毫秒)作为种子。并且,将具有相同的排列。现在,你尝试从两个不同的生成器中获得第一个随机数,它们可能是相同的。
为了避免出现这种情况,请创建一个public static final Random
变量并在任何地方使用它。
答案 3 :(得分:0)
为了使这个实现健壮并且不可能生成可预测的随机数,应该在shuffler类之外的静态方法中定义生成器类。因此,在任何给定时间都不会创建两个或更多个发生器的对象。因此最小化生成相同随机数的可能性。