网站首页 > 精选教程 正文
Java SE 中包含的随机数生成器更准确地称为伪随机数生成器(PRNGs)。它们基于确定性算法创建一系列数字。
最重要的接口和类是 RandomGenerator,它使您能够根据 PRNG 算法生成各种基本类型的随机数;以及 RandomGeneratorFactory,它使您能够基于除算法名称以外的特征创建 PRNGs。
有关 Java SE 中实现的 PRNGs 的更详细信息,请参阅 java.util.random 包。
PRNG 的特点
因为伪随机数生成器(PRNGs)基于算法而不是“随机”的物理源生成值序列,所以这个序列最终会重新开始。PRNG 在重新开始之前生成的值的数量称为周期。
PRNG 的状态周期包括 PRNG 可以生成的所有可能值的序列。PRNG 的状态是其状态周期中最后生成的值的位置。
通常,为了生成一个值,PRNG 基于先前生成的值。然而,一些 PRNG 可以在不计算任何中间值的情况下在序列中生成多个值。这些称为可跳跃的 PRNG,因为它们可以跳到序列中远处的位置,通常是固定距离,一般为 2^64。可跳跃 PRNG 可以跳得更远,通常是 2^128 个值。任意可跳跃的 PRNG 可以跳到生成的值序列中的任何值。
java.util.Random 类与其他 PRNG 的比较
java.util.random.RandomGeneratorFactory 类使您能够创建各种 PRNGs,其中许多位于 jdk.random 包中。jdk.random 中的 PRNGs 与 java.util.Random 类之间最显著的区别是 Random 具有非常短的周期:仅 2^48 个值。
使用 RandomGenerator 接口生成伪随机数
以下示例演示了创建 PRNG 并使用它生成随机数的基本方法:
RandomGenerator random1 = RandomGenerator.of("Random");
long value1 = random1.nextLong();
System.out.println(value1);
它使用了 RandomGenerator.of(String) 方法。该方法的参数是 PRNG 的算法名称。Java SE 包含许多 PRNG 类。不过,与 Random 不同的是,其中大多数都位于 jdk.random 包中。
RandomGenerator 接口包含许多方法,如 nextLong()、nextInt()、nextDouble() 和 nextBoolean(),用于生成各种原始数据类型的随机数。
以下示例演示了如何使用 RandomGeneratorFactory 类创建 PRNG:
RandomGeneratorFactory<RandomGenerator> factory2 =
RandomGeneratorFactory.of("SecureRandom");
RandomGenerator random2 = factory2.create();
long value2 = random2.nextLong();
System.out.println(value2);
要获取 Java SE 实现的 PRNG 列表,请调用 RandomGeneratorFactory.all() 方法:
RandomGeneratorFactory.all()
.map(f -> f.name())
.sorted()
.forEach(n -> System.out.println(n));
该方法返回所有可用的 RandomGeneratorFactory 实例的流。
您可以使用 RandomGeneratorFactory 类根据除算法名称以外的特征创建 PRNG。以下示例查找具有最长周期的 PRNG,并基于此特征创建 RandomGeneratorFactory:
RandomGeneratorFactory<RandomGenerator> greatest =
RandomGeneratorFactory
.all()
.sorted((f, g) -> g.period().compareTo(f.period()))
.findFirst()
.orElse(RandomGeneratorFactory.of("Random"));
System.out.println(greatest.name());
System.out.println(greatest.group());
System.out.println(greatest.create().nextLong());
在多线程应用程序中生成伪随机数
如果您的应用程序中有多个线程使用 PRNG 生成值序列,那么您希望确保这些序列不会包含相同的值,特别是如果它们使用相同的 PRNG 算法。 (您希望使用相同的 PRNG 算法,以确保您应用程序的所有伪随机数序列具有相同的统计特性。)可分割、可跳跃和可跳跃的 PRNG 对此非常理想;它们可以创建具有相同统计特性且在统计上相互独立的生成器流。
有两种技术可以将 PRNGs 支持到您的应用程序中。一种是在应用程序需要分叉一个新线程时动态创建一个新的生成器。另一种是基于初始 RandomGenerator 创建一个 RandomGenerator 对象流,然后将流中的每个 RandomGenerator 对象映射到自己的线程。
动态创建新的生成器
如果您正在使用实现 RandomGenerator.SplittableGenerator 接口的 PRNG,则当您的应用程序中运行的线程需要分叉一个新线程时,调用 split() 方法。它会创建一个具有与原始生成器相同属性的新生成器。它通过将原始生成器的周期分割成两部分来实现这一点;每个分割部分都专门供原始或新生成器的独占使用。
以下示例使用了实现 RandomGenerator.SplittableGenerator 接口的 L128X1024MixRandom PRNG。IntStream 流表示打算在不同线程上运行的任务。
int NUM_PROCESSES = 100;
RandomGeneratorFactory<SplittableGenerator> factory =
RandomGeneratorFactory.of("L128X1024MixRandom");
SplittableGenerator random = factory.create();
IntStream processes = IntStream.rangeClosed(1, NUM_PROCESSES);
processes.parallel().forEach(p -> {
RandomGenerator r = random.split();
System.out.println(p + ": " + r.nextLong());
});
可分割的 PRNG 通常具有较大的周期,以确保从分割产生的新对象使用不同的状态周期。但即使两个实例“意外地”使用相同的状态周期,它们也很可能遍历共享状态周期的不同区域。
创建生成器流
如果初始生成器实现了 RandomGenerator.StreamableGenerator 接口,则调用 rngs() 方法、jumps() 方法(对于可跳跃生成器),或 leaps() 方法(对于可跳跃生成器),以创建生成器流。在流上调用 map() 方法,将每个生成器分配给自己的线程。
当调用 jumps() 方法时,生成器通过在其状态周期内向前跳跃一个大的固定距离来改变其状态,然后基于生成器的新状态创建一个新的生成器。生成器重复跳跃和创建生成器,从而创建生成器流。leaps() 方法类似;跳跃的大小要大得多。
以下示例创建一个可跳跃的生成器,然后通过调用 jumps() 方法基于此初始生成器创建生成器流。流中的前几个生成器(由 NUM_TASKS 定义)包装在 Task 实例中,然后每个 Task 在其自己的线程中运行。
int NUM_TASKS = 10;
RandomGeneratorFactory<JumpableGenerator> factory =
RandomGeneratorFactory.of("Xoshiro256PlusPlus");
JumpableGenerator random = factory.create();
class Task implements Runnable {
private int p;
private RandomGenerator r;
public Task(RandomGenerator prng) {
r = prng;
}
public void run() {
System.out.println(r.nextLong());
}
}
List<Thread> taskList = random
.jumps()
.limit(NUM_TASKS)
.map(prng -> new Thread(new Task(prng)))
.collect(Collectors.toList());
taskList.stream().forEach(t -> t.start());
选择 PRNG 算法
对于不需要加密安全算法的应用程序(例如物理模拟、机器学习和游戏),java.util.random 包提供了多种 RandomGenerator 接口的实现,这些实现侧重于一个或多个 PRNG 属性,包括速度、空间、周期、偶然相关性和等分布。
注意:随着 PRNG 算法的发展,Java SE 可能会添加新的 PRNG 算法并废弃旧的算法。建议不要使用已废弃的算法;它们可能会在将来的 Java SE 发布中被移除。通过调用 RandomGenerator.isDeprecated() 或 RandomGeneratorFactory.isDeprecated() 方法来检查算法是否已被废弃。
加密安全
对于需要加密安全的随机数生成器算法的应用程序,请使用 java.security 包中的 SecureRandom 类。
有关详细信息,请参阅 Java 平台标准版安全开发人员指南中的 SecureRandom 类。
一般用途
对于没有特殊要求的应用,L64X128MixRandom 很好地平衡了速度、空间和周期。如果使用得当(每个线程都有一个单独的实例),它适用于单线程和多线程应用程序。
单线程、高性能
对于单线程应用程序来说,Xoroshiro128PlusPlus体积小、速度快、周期足够长。
32 位应用程序
对于在 32 位环境中运行且仅使用一个或少量线程的应用程序,L32X64StarStarRandom 或 L32X64MixRandom 是不错的选择。
具有静态线程的多线程应用程序
对于在计算开始时分配了许多线程的应用程序,请考虑使用可跳跃生成器,如 Xoroshiro128PlusPlus 或 Xoshiro256PlusPlus,或者可分割生成器,如 L64X128MixRandom 或 L64X256MixRandom。如果您的应用程序只使用来自均匀分布的浮点值,其中不需要超过 32 位的浮点精度,并且不需要精确的等分布,则经典且经过良好研究的算法 MRG32k3a 可能是合适的选择。
具有动态线程的多线程应用程序
对于动态创建许多线程的应用程序(可能通过使用分割器),建议使用可分割生成器,例如 L64X128MixRandom 或 L64X256MixRandom。
如果动态创建的生成器数量可能非常大(数百万或更多),则使用 L128X128MixRandom 或 L128X256MixRandom 等生成器将使两个实例使用相同状态循环的可能性大大降低。
连续生成值的元组
对于使用连续生成的值元组的应用程序,请考虑选择一个 k-等分布的生成器,其中 k 至少与生成的元组长度相同。例如,生成器 L64X256MixRandom 被证明是 4-等分布的,这意味着您可以有一个包含四个值的元组序列,并且这些元组将是均匀分布的(任何 4-元组出现在序列中的机会是相等的)。同样,L64X1024MixRandom 被证明是 16-等分布的。
大排列
对于生成大型排列的应用程序,请考虑选择一个周期远远大于可能排列总数的生成器;否则,将无法生成一些预期的排列。例如,如果目标是洗牌一副有 52 张牌的牌组,可能排列的总数是 52!(52 的阶乘),约为 2^225.58,因此最好使用一个周期大约为 2^256 或更大的生成器,比如 L64X256MixRandom、L64X1024MixRandom、L128X256MixRandom 或 L128X1024MixRandom。
猜你喜欢
- 2024-11-16 Java编程从零开始07 数组的基本算法(查找和排序)
- 2024-11-16 Java练习:一个猜数游戏(java猜数游戏程序)
- 2024-11-16 Java中生成唯一ID的方法(java 生成id)
- 2024-11-16 617、java类,对象,集合的介绍(java中对象是什么)
- 2024-11-16 聊聊最近面试中遇到的算法题:公平的随机
- 2024-11-16 Java实现7种常见密码算法(java密码加密哪种方式最安全)
- 2024-11-16 JAVA入门:零基础实现幸运抽奖功能
- 2024-11-16 Hive 自定UDF函数,生成 32 位随机数
- 2024-11-16 四十三、Java常用类-Random类:深入理解与实践指南
- 2024-11-16 在程序中用的随机数,足够随机吗?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)