Apache Commons RNG 1.7 常用 API 与随机源详解
详解 Apache Commons RNG 1.7 的常用 API 与随机源体系,含 57 个 RandomSource 枚举全量清单与算法族分类、UniformRandomProvider 全套取数方法、种子创建与可复现性实测、可跳可分裂可流式 provider 与状态保存恢复,全部附真实运行回显。
Apache Commons RNG 1.7 常用 API 与随机源详解
目标:系统讲解 Apache Commons RNG 1.7 的常用 API——57 个 RandomSource 随机源、UniformRandomProvider 全套取数方法、种子创建与可复现性、可跳/可分裂/可流式 provider 与状态保存恢复,每个 API 均配真实运行回显;本文全部命令与回显均已在 Ubuntu 24.04 + JDK 17 环境实测通过。
Commons RNG 的 API 面是一个清晰的三层结构:应用层只接触两个模块——simple 模块的 RandomSource 枚举负责「创建哪个算法的生成器、用什么种子」,client-api 模块的 UniformRandomProvider 及其扩展接口负责「取数、跳变、分裂、流式」;core 模块是全部算法实现,官方明确不建议应用直接访问。本篇用一个分组演示工程逐组实测,分组名为命令行参数(sources/basic/seed/repro/stream/state),采样器体系(sampling 模块)在姊妹篇《Apache Commons RNG 1.7 随机数生成示例与最佳实践详解》中专门展开。
一、环境信息
本节固定可复现基线,后续命令均在此环境执行。
- 操作系统:Ubuntu 24.04.5 LTS(PRETTY_NAME="Ubuntu 24.04.5 LTS")
- 架构:x86_64
- JDK:OpenJDK 17.0.20.1(Eclipse Temurin 发行版)
- 构建工具:Apache Maven 3.9.16
- 组件版本:Apache Commons RNG 1.7(client-api、core、simple、sampling 四模块实测装载)
- Maven 坐标:
org.apache.commons:commons-rng-simple:1.7、org.apache.commons:commons-rng-sampling:1.7 - 工程目录:
/root/rng-demo(主类RngDemo.java)
环境确认(实时回显):
java -version 2>&1 | head -1 && mvn -version | head -1
openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
二、API 总览
本节先给出 1.7 的完整 API 地图,后续各章逐一实测。应用侧核心类型只有一张表:
| 类型 | 所属模块 | 职责 | 本文实测章节 |
|---|---|---|---|
RandomSource(枚举) | simple | 57 种算法的工厂:create/createLong/createInt | 第四章 |
UniformRandomProvider | client-api | 基础取数:nextInt/nextLong/nextDouble/nextFloat/nextBoolean/nextBytes + 流式默认方法 | 第五章 |
RestorableUniformRandomProvider | client-api | 状态保存与恢复(saveState/restoreState) | 第八章 |
JumpableUniformRandomProvider | client-api | 生成远距离快进副本(jump/jumps/longJump) | 第七章 |
LongJumpableUniformRandomProvider | client-api | 生成超远距离(2^128 级)快进副本 | 第七章 |
ArbitrarilyJumpableUniformRandomProvider | client-api | 按任意距离跳变(jump(distance)/jumpPowerOfTwo) | 第七章 |
SplittableUniformRandomProvider | client-api | 分裂出新独立实例(split/splits),并行流首选 | 第七章 |
ThreadLocalRandomSource | simple | 每线程自动持有独立生成器 | 第七章 |
RandomProviderState | client-api | saveState 返回的状态句柄 | 第八章 |
Sampler 体系(ContinuousSampler/DiscreteSampler/NormalizedGaussianSampler 等) | sampling | 分布采样,工厂方法 of(...) | 姊妹篇采样篇 |
💡 技术拓展:为什么要有几十种随机源算法?随机数生成器算法族各有取舍,常用族系对比如下:
| 算法族 | 代表算法(1.7 枚举) | 特点 | 适用 |
|---|---|---|---|
| 线性同余/老牌通用 | JDK、ISAAC、KISS、MWC_256 | 历史悠久、实现简单,统计质量参差 | 兼容 JDK 序列(JDK 枚举) |
| Twister 族 | MT、MT_64、WELL_512_A 至 WELL_44497_B | 周期 2^19937-1,质量稳定,状态大(2.5KB) | 通用模拟;WELL 修复了 MT 的劣化恢复缺陷 |
| XorShift 族 | XOR_SHIFT_1024_S(已弃用)、XOR_SHIFT_1024_S_PHI | 极快;原版输出存在线性相关缺陷 | 用 PHI 版本,勿用弃用版 |
| Xoshiro/XoRoShiRo 族 | XO_SHI_RO_128/256/512、XO_RO_SHI_RO_64/1024 各变体 | 当代标杆:极快、质量好、状态紧凑 | 默认推荐;天然支持 jump |
| PCG 族 | PCG_XSH_RR_32、PCG_RXS_M_XS_64 等(含 _OS 原始种子变体) | 小状态大周期、统计优秀 | 内存敏感场景 |
| LXM 族 | L64_X256_MIX、L128_X1024_MIX 等 8 个 | JDK 17 内置选择器的同族算法;唯一支持 Splittable | 并行任务分裂 |
| 计数器式 | PHILOX_4X32、PHILOX_4X64 | 可按任意距离跳变(无状态寻址),唯一 ArbitrarilyJumpable | 蒙特卡洛分块、可寻址采样 |
| 小快族 | MSWS、SFC_32/64、JSF_32/64 | 极简实现、速度快 | 资源受限场景 |
本文实测覆盖:Twister(MT/MT_64)、Xoshiro(XO_SHI_RO_256_SS 等)、LXM(L64_X256_MIX)、Philox(PHILOX_4X64)、SplitMix(SPLIT_MIX_64)、JDK。
三、演示工程与主类
本节建立演示工程:pom 引入 simple 与 sampling 两模块,主类 RngDemo 按命令行参数分组执行(sources/basic/seed/repro/sampling/stream/state),后续各章的回显均来自该类的对应方法。
工程 pom(文件路径 /root/rng-demo/pom.xml,依赖与《快速入门》一致,此处不再重复展示)与主类 RngDemo.java(文件路径 /root/rng-demo/src/main/java/RngDemo.java)完整源码:
import org.apache.commons.rng.ArbitrarilyJumpableUniformRandomProvider;
import org.apache.commons.rng.JumpableUniformRandomProvider;
import org.apache.commons.rng.LongJumpableUniformRandomProvider;
import org.apache.commons.rng.RandomProviderState;
import org.apache.commons.rng.RestorableUniformRandomProvider;
import org.apache.commons.rng.SplittableUniformRandomProvider;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.apache.commons.rng.sampling.distribution.AhrensDieterExponentialSampler;
import org.apache.commons.rng.sampling.distribution.AhrensDieterMarsagliaTsangGammaSampler;
import org.apache.commons.rng.sampling.distribution.BoxMullerNormalizedGaussianSampler;
import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
import org.apache.commons.rng.sampling.distribution.DirichletSampler;
import org.apache.commons.rng.sampling.distribution.GaussianSampler;
import org.apache.commons.rng.sampling.distribution.GeometricSampler;
import org.apache.commons.rng.sampling.distribution.MarsagliaTsangWangDiscreteSampler;
import org.apache.commons.rng.sampling.distribution.NormalizedGaussianSampler;
import org.apache.commons.rng.sampling.distribution.PoissonSampler;
import org.apache.commons.rng.sampling.distribution.ZigguratNormalizedGaussianSampler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class RngDemo {
public static void main(String[] args) {
String group = args.length > 0 ? args[0] : "all";
if (group.equals("sources") || group.equals("all")) sources();
if (group.equals("basic") || group.equals("all")) basic();
if (group.equals("seed") || group.equals("all")) seed();
if (group.equals("repro") || group.equals("all")) repro();
if (group.equals("sampling") || group.equals("all")) sampling();
if (group.equals("stream") || group.equals("all")) stream();
if (group.equals("state") || group.equals("all")) state();
}
static void sources() {
System.out.println("== RandomSource 枚举全量清单(共 " + RandomSource.values().length + " 项) ==");
System.out.println("接口标记: J=Jumpable L=LongJumpable A=ArbitrarilyJumpable S=Splittable R=Restorable");
for (RandomSource s : RandomSource.values()) {
try {
UniformRandomProvider p = s.create();
StringBuilder flag = new StringBuilder();
if (p instanceof JumpableUniformRandomProvider) flag.append('J'); else flag.append('-');
if (p instanceof LongJumpableUniformRandomProvider) flag.append('L'); else flag.append('-');
if (p instanceof ArbitrarilyJumpableUniformRandomProvider) flag.append('A'); else flag.append('-');
if (p instanceof SplittableUniformRandomProvider) flag.append('S'); else flag.append('-');
if (p instanceof RestorableUniformRandomProvider) flag.append('R'); else flag.append('-');
System.out.printf("%-20s %-8s %s%n", s.name(), flag, p.getClass().getSimpleName());
} catch (RuntimeException e) {
System.out.printf("%-20s %-8s create()需专用参数: %s%n", s.name(), "----", e.getMessage());
}
}
}
static void basic() {
UniformRandomProvider rng = RandomSource.create(RandomSource.MT, 20261015L);
System.out.println("== UniformRandomProvider 基础方法(MT, seed=20261015) ==");
System.out.println("nextInt() = " + rng.nextInt());
System.out.println("nextInt(100) = " + rng.nextInt(100));
System.out.println("nextInt(10, 20) = " + rng.nextInt(10, 20));
System.out.println("nextLong() = " + rng.nextLong());
System.out.println("nextLong(1000000) = " + rng.nextLong(1000000L));
System.out.println("nextLong(-50, 50) = " + rng.nextLong(-50L, 50L));
System.out.println("nextDouble() = " + rng.nextDouble());
System.out.println("nextDouble(2.5, 7.5) = " + rng.nextDouble(2.5, 7.5));
System.out.println("nextFloat() = " + rng.nextFloat());
System.out.println("nextBoolean() = " + rng.nextBoolean());
byte[] b = new byte[8];
rng.nextBytes(b);
StringBuilder hex = new StringBuilder();
for (byte x : b) hex.append(String.format("%02x", x));
System.out.println("nextBytes(8) = 0x" + hex);
}
static void seed() {
System.out.println("== 种子创建 ==");
long l1 = RandomSource.createLong();
long l2 = RandomSource.createLong();
System.out.println("createLong() 第1次 = " + l1);
System.out.println("createLong() 第2次 = " + l2 + " 两次不同=" + (l1 != l2));
System.out.println("createInt() = " + RandomSource.createInt());
UniformRandomProvider r1 = RandomSource.create(RandomSource.MT);
UniformRandomProvider r2 = RandomSource.create(RandomSource.MT);
System.out.println("create(MT) 随机种子, 两次首个 nextLong = " + r1.nextLong() + " / " + r2.nextLong());
UniformRandomProvider fixed = RandomSource.create(RandomSource.MT, 42L);
System.out.println("create(MT, 42L) 固定种子首个 nextLong = " + fixed.nextLong());
byte[] bseed = {0x1a, 0x2b, 0x3c, 0x4d};
UniformRandomProvider bs = RandomSource.create(RandomSource.XO_SHI_RO_128_PLUS, bseed);
System.out.println("create(XO_SHI_RO_128_PLUS, byte[4]) 首个 nextLong = " + bs.nextLong());
UniformRandomProvider sh1 = RandomSource.create(RandomSource.MT, new byte[]{1, 2});
UniformRandomProvider sh2 = RandomSource.create(RandomSource.MT, new byte[]{1, 2});
System.out.println("MT 配 2 字节种子(短于状态)两次首值 = " + sh1.nextLong() + " / " + sh2.nextLong()
+ " 一致=" + (sh1.nextLong() == sh2.nextLong()));
}
static void repro() {
System.out.println("== 同种子可复现性(MT) ==");
long[] a = new long[5];
UniformRandomProvider r1 = RandomSource.create(RandomSource.MT, 987654321L);
for (int i = 0; i < 5; i++) a[i] = r1.nextLong();
System.out.println("第1次 seed=987654321 : " + Arrays.toString(a));
long[] b = new long[5];
UniformRandomProvider r2 = RandomSource.create(RandomSource.MT, 987654321L);
for (int i = 0; i < 5; i++) b[i] = r2.nextLong();
System.out.println("第2次 seed=987654321 : " + Arrays.toString(b));
System.out.println("两次序列完全一致 = " + Arrays.equals(a, b));
long[] c = new long[5];
UniformRandomProvider r3 = RandomSource.create(RandomSource.MT, 987654322L);
for (int i = 0; i < 5; i++) c[i] = r3.nextLong();
System.out.println("种子改为 987654322 : " + Arrays.toString(c));
System.out.println("与第1次一致 = " + Arrays.equals(a, c));
long[] d = new long[5];
UniformRandomProvider r4 = RandomSource.create(RandomSource.XO_SHI_RO_256_SS, 987654321L);
for (int i = 0; i < 5; i++) d[i] = r4.nextLong();
System.out.println("换算法 XO_SHI_RO_256_SS 同种子: " + Arrays.toString(d));
}
static void sampling() {
int n = 200000;
System.out.println("== 分布采样实测(N=" + n + ") ==");
UniformRandomProvider rng = RandomSource.create(RandomSource.XO_SHI_RO_256_SS, 2026L);
double[] u = fill(n, ContinuousUniformSampler.of(rng, 0.0, 10.0)::sample);
report("ContinuousUniformSampler(0,10)", u, 5.0, 100.0 / 12.0);
NormalizedGaussianSampler zig = ZigguratNormalizedGaussianSampler.of(rng);
double first = zig.sample();
double[] g = fillAfter(n, zig::sample, first);
report("ZigguratNormalizedGaussian", g, 0.0, 1.0);
StringBuilder p5 = new StringBuilder("[");
for (int i = 0; i < 5; i++) p5.append(String.format("%.4f", zig.sample())).append(i < 4 ? ", " : "]");
System.out.println(" Ziggurat 预览 5 个: " + p5);
double[] bm = fill(n, BoxMullerNormalizedGaussianSampler.of(rng)::sample);
report("BoxMullerNormalizedGaussian", bm, 0.0, 1.0);
double[] gs = fill(n, GaussianSampler.of(ZigguratNormalizedGaussianSampler.of(rng), 100.0, 15.0)::sample);
report("GaussianSampler(mean=100,sd=15)", gs, 100.0, 225.0);
double[] ex = fill(n, AhrensDieterExponentialSampler.of(rng, 2.0)::sample);
report("Exponential(mean=2)", ex, 2.0, 4.0);
double[] ga = fill(n, AhrensDieterMarsagliaTsangGammaSampler.of(rng, 3.0, 2.0)::sample);
report("Gamma(shape=3,scale=2)", ga, 6.0, 12.0);
double[] po = fill(n, PoissonSampler.of(rng, 8.0)::sample);
report("Poisson(mean=8)", po, 8.0, 8.0);
double[] bi = fill(n, MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, 20, 0.3)::sample);
report("Binomial(20, 0.3)", bi, 6.0, 4.2);
double[] ge = fill(n, GeometricSampler.of(rng, 0.2)::sample);
report("Geometric(p=0.2)", ge, 4.0, 20.0);
DirichletSampler dir = DirichletSampler.symmetric(rng, 4, 2.0);
double[] s1 = new double[4];
int m = 100000;
for (int i = 0; i < m; i++) {
double[] v = dir.sample();
for (int k = 0; k < 4; k++) s1[k] += v[k];
}
System.out.printf("Dirichlet.symmetric(4, alpha=2) 各分量均值: [%.4f, %.4f, %.4f, %.4f](理论 0.25)和=%.4f%n",
s1[0] / m, s1[1] / m, s1[2] / m, s1[3] / m, (s1[0] + s1[1] + s1[2] + s1[3]) / m);
}
static double[] fill(int n, java.util.function.DoubleSupplier s) {
double[] x = new double[n];
for (int i = 0; i < n; i++) x[i] = s.getAsDouble();
return x;
}
static double[] fillAfter(int n, java.util.function.DoubleSupplier s, double first) {
double[] x = new double[n];
x[0] = first;
for (int i = 1; i < n; i++) x[i] = s.getAsDouble();
return x;
}
static void report(String name, double[] x, double tm, double tv) {
double sum = 0;
double min = Double.MAX_VALUE, max = -Double.MAX_VALUE;
for (double v : x) {
sum += v;
if (v < min) min = v;
if (v > max) max = v;
}
double mean = sum / x.length;
double ss = 0;
for (double v : x) ss += (v - mean) * (v - mean);
double var = ss / (x.length - 1);
System.out.printf("%-32s mean=%9.4f var=%9.4f min=%10.4f max=%10.4f 理论 mean=%.4f var=%.4f%n",
name, mean, var, min, max, tm, tv);
}
static void stream() {
System.out.println("== provider 接口能力探测(seed=123456789) ==");
RandomSource[] probes = {RandomSource.MT, RandomSource.MT_64, RandomSource.SPLIT_MIX_64,
RandomSource.XO_RO_SHI_RO_128_PLUS, RandomSource.XO_SHI_RO_256_SS, RandomSource.L64_X256_MIX,
RandomSource.PHILOX_4X64};
for (RandomSource s : probes) {
UniformRandomProvider p = s.create(123456789L);
System.out.printf("%-22s J=%-5s L=%-5s A=%-5s S=%s%n", s.name(),
p instanceof JumpableUniformRandomProvider,
p instanceof LongJumpableUniformRandomProvider,
p instanceof ArbitrarilyJumpableUniformRandomProvider,
p instanceof SplittableUniformRandomProvider);
}
System.out.println("-- JumpableUniformRandomProvider(XO_SHI_RO_256_SS, seed=42) --");
JumpableUniformRandomProvider jumpable = (JumpableUniformRandomProvider) RandomSource.create(RandomSource.XO_SHI_RO_256_SS, 42L);
int first = jumpable.nextInt();
int second = jumpable.nextInt();
JumpableUniformRandomProvider advanced = (JumpableUniformRandomProvider) jumpable.jump();
System.out.println("原生成器第1/2个 nextInt = " + first + " / " + second);
System.out.println("jump() 生成快进副本, 副本首个 nextInt = " + advanced.nextInt());
List<Integer> jumps = new ArrayList<>();
jumpable.jumps(3).forEach(p -> jumps.add(p.nextInt()));
System.out.println("jumps(3) 三个副本首个 nextInt = " + jumps);
LongJumpableUniformRandomProvider lj = (LongJumpableUniformRandomProvider) jumpable;
System.out.println("longJump() 副本首个 nextInt = " + lj.longJump().nextInt());
System.out.println("-- SplittableUniformRandomProvider(L64_X256_MIX, seed=42) --");
SplittableUniformRandomProvider root = (SplittableUniformRandomProvider) RandomSource.create(RandomSource.L64_X256_MIX, 42L);
int rv = root.nextInt();
SplittableUniformRandomProvider child = root.split();
System.out.println("root 首个 nextInt = " + rv + ", split() 子实例首个 nextInt = " + child.nextInt());
List<Long> four = root.splits(4).map(p -> (long) p.nextInt()).collect(Collectors.toList());
System.out.println("splits(4) 四个分支首个 nextInt = " + four);
System.out.println("四分支首值互不相同 = " + (four.stream().distinct().count() == 4));
long parallelSum = root.splits().parallel().limit(8).mapToLong(p -> p.nextLong()).sum();
System.out.println("splits().parallel().limit(8) 并行累加 8 个分支 nextLong 之和 = " + parallelSum);
System.out.println("-- ArbitrarilyJumpableUniformRandomProvider(PHILOX_4X64, seed=42) --");
ArbitrarilyJumpableUniformRandomProvider aj = (ArbitrarilyJumpableUniformRandomProvider) RandomSource.create(RandomSource.PHILOX_4X64, 42L);
ArbitrarilyJumpableUniformRandomProvider aj2 = aj.jump(1099511627776.0);
System.out.println("PHILOX_4X64 jump(2^40) 副本首个 nextLong = " + aj2.nextLong());
ArbitrarilyJumpableUniformRandomProvider aj3 = aj.jumpPowerOfTwo(20);
System.out.println("PHILOX_4X64 jumpPowerOfTwo(20) 副本首个 nextLong = " + aj3.nextLong());
System.out.println("-- UniformRandomProvider 流式默认方法(SPLIT_MIX_64, seed=42) --");
UniformRandomProvider u = RandomSource.create(RandomSource.SPLIT_MIX_64, 42L);
String d = u.doubles().limit(5).mapToObj(v -> String.format("%.4f", v)).collect(Collectors.joining(", "));
System.out.println("doubles().limit(5) = [" + d + "]");
List<Integer> dice = u.ints(1, 7).limit(10).boxed().collect(Collectors.toList());
System.out.println("ints(1, 7).limit(10) 模拟掷骰子 = " + dice);
}
static void state() {
System.out.println("== RestorableUniformRandomProvider 状态保存与恢复(MT, seed=555) ==");
RestorableUniformRandomProvider rng = (RestorableUniformRandomProvider) RandomSource.create(RandomSource.MT, 555L);
RandomProviderState st = rng.saveState();
int a1 = rng.nextInt();
int a2 = rng.nextInt();
int a3 = rng.nextInt();
System.out.println("saveState 后生成 3 个: " + a1 + ", " + a2 + ", " + a3);
rng.restoreState(st);
int b1 = rng.nextInt();
int b2 = rng.nextInt();
int b3 = rng.nextInt();
System.out.println("restoreState 后生成 3 个: " + b1 + ", " + b2 + ", " + b3);
System.out.println("回滚后序列一致 = " + (a1 == b1 && a2 == b2 && a3 == b3));
}
}
编译验证(实时回显):
cd /root/rng-demo && mvn -q -B compile; echo EXIT=$?
EXIT=0
各分组按 java -cp <classpath> RngDemo <分组名> 运行,classpath 含四个模块 jar(simple、core、client-api、sampling),拼法同《快速入门》2.3 节。
四、RandomSource 枚举与随机源全览
RandomSource 是 simple 模块中的枚举,1.7 共 57 个枚举值,是创建生成器的唯一推荐入口。sources 分组实测枚举每一个值:无参创建实例,用 instanceof 打出其实现的扩展接口标记,并打印实现类名(输出节选为实测完整回显,共 59 行):
java -Dfile.encoding=UTF-8 -cp $CP RngDemo sources
== RandomSource 枚举全量清单(共 57 项) ==
接口标记: J=Jumpable L=LongJumpable A=ArbitrarilyJumpable S=Splittable R=Restorable
JDK ----R JDKRandom
WELL_512_A ----R Well512a
WELL_1024_A ----R Well1024a
WELL_19937_A ----R Well19937a
WELL_19937_C ----R Well19937c
WELL_44497_A ----R Well44497a
WELL_44497_B ----R Well44497b
MT ----R MersenneTwister
ISAAC ----R ISAACRandom
SPLIT_MIX_64 ----R SplitMix64
XOR_SHIFT_1024_S J---R XorShift1024Star
TWO_CMRES ----R TwoCmres
TWO_CMRES_SELECT ---- create()需专用参数: wrong number of arguments
MT_64 ----R MersenneTwister64
MWC_256 ----R MultiplyWithCarry256
KISS ----R KISSRandom
XOR_SHIFT_1024_S_PHI J---R XorShift1024StarPhi
XO_RO_SHI_RO_64_S ----R XoRoShiRo64Star
XO_RO_SHI_RO_64_SS ----R XoRoShiRo64StarStar
XO_SHI_RO_128_PLUS JL--R XoShiRo128Plus
XO_SHI_RO_128_SS JL--R XoShiRo128StarStar
XO_RO_SHI_RO_128_PLUS JL--R XoRoShiRo128Plus
XO_RO_SHI_RO_128_SS JL--R XoRoShiRo128StarStar
XO_SHI_RO_256_PLUS JL--R XoShiRo256Plus
XO_SHI_RO_256_SS JL--R XoShiRo256StarStar
XO_SHI_RO_512_PLUS JL--R XoShiRo512Plus
XO_SHI_RO_512_SS JL--R XoShiRo512StarStar
PCG_XSH_RR_32 ----R PcgXshRr32
PCG_XSH_RS_32 ----R PcgXshRs32
PCG_RXS_M_XS_64 ----R PcgRxsMXs64
PCG_MCG_XSH_RR_32 ----R PcgMcgXshRr32
PCG_MCG_XSH_RS_32 ----R PcgMcgXshRs32
MSWS ----R MiddleSquareWeylSequence
SFC_32 ----R DotyHumphreySmallFastCounting32
SFC_64 ----R DotyHumphreySmallFastCounting64
JSF_32 ----R JenkinsSmallFast32
JSF_64 ----R JenkinsSmallFast64
XO_SHI_RO_128_PP JL--R XoShiRo128PlusPlus
XO_RO_SHI_RO_128_PP JL--R XoRoShiRo128PlusPlus
XO_SHI_RO_256_PP JL--R XoShiRo256PlusPlus
XO_SHI_RO_512_PP JL--R XoShiRo512PlusPlus
XO_RO_SHI_RO_1024_PP JL--R XoRoShiRo1024PlusPlus
XO_RO_SHI_RO_1024_S JL--R XoRoShiRo1024Star
XO_RO_SHI_RO_1024_SS JL--R XoRoShiRo1024StarStar
PCG_XSH_RR_32_OS ----R PcgXshRr32
PCG_XSH_RS_32_OS ----R PcgXshRs32
PCG_RXS_M_XS_64_OS ----R PcgRxsMXs64
L64_X128_SS JL-SR L64X128StarStar
L64_X128_MIX JL-SR L64X128Mix
L64_X256_MIX JL-SR L64X256Mix
L64_X1024_MIX JL-SR L64X1024Mix
L128_X128_MIX JL-SR L128X128Mix
L128_X256_MIX JL-SR L128X256Mix
L128_X1024_MIX JL-SR L128X1024Mix
L32_X64_MIX JL-SR L32X64Mix
PHILOX_4X32 JLA-R Philox4x32
PHILOX_4X64 JLA-R Philox4x64
清单解读(实测归纳):
- 接口能力分布:只有 Xoshiro/XoRoShiRo 族(JL)、LXM 族(JL-S)、Philox(JLA)支持跳变;Splittable 仅 LXM 族 8 个;ArbitrarilyJumpable 仅 PHILOX 两个;全部可创建实例都实现 Restorable(状态可保存恢复)。
- MT 不可跳:MersenneTwister/MT_64 只有 Restorable——想要「可跳的 MT 级质量」应选 XO_SHI_RO_512 系列。
- XOR_SHIFT_1024_S 已弃用:其输出序列与 XO_RO_SHI_RO_1024_S 存在已知线性相关缺陷,1.7 源码标记
@Deprecated,新代码用XOR_SHIFT_1024_S_PHI。 - _OS 变体:PCG 三个
_OS(original seed)枚举与对应标准变体实现类相同(如都是PcgXshRr32),差别在种子处理——OS 变体直接以原始种子初始化状态,不经过库默认的种子扩展,供需要精确控制 PCG 原始种子语义的场景使用。 - TWO_CMRES_SELECT 特例:需显式传入两个子周期选择参数,无参
create()抛IllegalArgumentException: wrong number of arguments(实测回显见上表第 13 行);普通用途用TWO_CMRES即可。
验证点:枚举总数 57 与官方发布说明一致;标记与实现在 1.7 中与 Javadoc 描述完全对应。
五、UniformRandomProvider 基础 API
UniformRandomProvider 是所有生成器的统一接口,覆盖 int/long/double/float/boolean/bytes 六类取数,且带区间重载与流式默认方法。basic 分组用固定种子 MT 实测全部方法(实时回显):
java -Dfile.encoding=UTF-8 -cp $CP RngDemo basic
== UniformRandomProvider 基础方法(MT, seed=20261015) ==
nextInt() = 243253777
nextInt(100) = 79
nextInt(10, 20) = 15
nextLong() = 7715127294584072146
nextLong(1000000) = 198837
nextLong(-50, 50) = -29
nextDouble() = 0.6668178322949391
nextDouble(2.5, 7.5) = 5.140883469312919
nextFloat() = 0.17672157
nextBoolean() = false
nextBytes(8) = 0x84d619248d13d53f
逐项说明语义要点:
- 区间重载统一为左闭右开
[origin, bound):nextInt(10, 20)实测返回 15 落在区间内;nextDouble(2.5, 7.5)返回 5.14。取负区间(nextLong(-50, 50))同样支持。 nextInt()全域 32 位、nextLong()全域 64 位,可取负值——与java.util.Random行为一致,便于对拍。nextBytes(byte[])把随机字节填入调用方提供的数组(本例 8 字节十六进制84d619248d13d53f),适合生成会话标识、IV 等非加密用途的字节串。- 带概率参数的
nextBoolean(p)未列于基础组,属 client-api 的扩展默认方法,语义为「以概率 p 返回 true」。 - 流式默认方法
ints()/longs()/doubles()(含区间重载)返回java.util.stream流,第七章实测。
验证点:所有返回值均在声明区间内;固定种子下重复运行输出逐字节一致。
六、种子创建与可复现性
RandomSource 提供三类创建入口:无种子的随机种子创建(create())、工具方法 createLong()/createInt() 生成随机种子、带种子的 create(seed)。种子支持 Long、Integer、int[]、byte[] 四种类型。seed 分组实测(实时回显):
java -Dfile.encoding=UTF-8 -cp $CP RngDemo seed
== 种子创建 ==
createLong() 第1次 = -2968080829408088506
createLong() 第2次 = 5894499799292533062 两次不同=true
createInt() = 1955424724
create(MT) 随机种子, 两次首个 nextLong = 7182871407869242980 / -5254808392942606722
create(MT, 42L) 固定种子首个 nextLong = -2927045846119549669
create(XO_SHI_RO_128_PLUS, byte[4]) 首个 nextLong = -6465045549583470468
MT 配 2 字节种子(短于状态)两次首值 = 303612072258272891 / 303612072258272891 一致=true
三个关键实测结论:
createLong()/createInt()每次调用产生不同值,用于「不指定种子」的场景(create(MT)内部即用它);生产环境随机种子应走这条路,而不是硬编码。- 短种子可用且可复现:MT 的内部状态是 624 个 int,但传入仅 2 字节的
byte[]{1,2}也能创建(库自动做种子扩展填充剩余状态),且同内容种子得到完全相同的序列(实测一致=true)。这解决了「种子类型长度必须匹配状态长度」的历史困惑。 - 固定种子
create(MT, 42L)首值恒为-2927045846119549669,跨 JVM、跨操作系统一致(本专栏 Ubuntu/CentOS 两篇安装指南的验证程序实测互证)。
固定种子与可复现性是测试与调试的基础。repro 分组实测「同种子两次生成序列一致」(实时回显):
java -Dfile.encoding=UTF-8 -cp $CP RngDemo repro
== 同种子可复现性(MT) ==
第1次 seed=987654321 : [6191537600306669016, 2090196379284387865, -8643758442901524787, 403713595580250213, 5547639704967621470]
第2次 seed=987654321 : [6191537600306669016, 2090196379284387865, -8643758442901524787, 403713595580250213, 5547639704967621470]
两次序列完全一致 = true
种子改为 987654322 : [1264674421301375319, 8474307082050497836, 6476263243348647426, -4544858320655458157, -5829003754129432960]
与第1次一致 = false
换算法 XO_SHI_RO_256_SS 同种子: [-968043694440775, -2371799455578155784, -5780694607329389342, 6959763153063108458, -1053903639099415643]
验证点:同种子同算法序列完全一致(true);种子 +1 后序列完全不同(false),说明不存在肉眼可见的序列相关;换算法后同种子序列也不同——可复现的前提是「同种子 + 同算法」二者同时固定,这是并行场景给每个 worker 分配「主种子 + worker 编号」派生种子的理论基础。
⚠️ 注意:种子值本身没有「好坏」,但把时间戳、进程号等可预测值当种子的做法在「防猜」场景不安全;Commons RNG 全系是伪随机数生成器,不能用于加密用途(密钥、令牌、盐),那些场景必须用
SecureRandom。
七、可跳、可分裂与流式 provider
并行程序不能让多个线程共享一个生成器实例(有竞争且序列交错),正确做法是给每个执行单元独立的序列。Commons RNG 提供三条路径:跳变(jump,同一序列的远距离分段)、分裂(split,树状派生独立序列)、线程本地(ThreadLocalRandomSource)。stream 分组先对 7 个代表算法做 instanceof 能力探测,再逐接口实测(实时回显):
java -Dfile.encoding=UTF-8 -cp $CP RngDemo stream
== provider 接口能力探测(seed=123456789) ==
MT J=false L=false A=false S=false
MT_64 J=false L=false A=false S=false
SPLIT_MIX_64 J=false L=false A=false S=false
XO_RO_SHI_RO_128_PLUS J=true L=true A=false S=false
XO_SHI_RO_256_SS J=true L=true A=false S=false
L64_X256_MIX J=true L=true A=false S=true
PHILOX_4X64 J=true L=true A=true S=false
-- JumpableUniformRandomProvider(XO_SHI_RO_256_SS, seed=42) --
原生成器第1/2个 nextInt = 204392214 / 360188718
jump() 生成快进副本, 副本首个 nextInt = 1829845630
jumps(3) 三个副本首个 nextInt = [323475203, 977058169, 1549589826]
longJump() 副本首个 nextInt = 1357925730
-- SplittableUniformRandomProvider(L64_X256_MIX, seed=42) --
root 首个 nextInt = 650821739, split() 子实例首个 nextInt = -660785044
splits(4) 四个分支首个 nextInt = [802354591, -1217965169, 800913460, -780944928]
四分支首值互不相同 = true
splits().parallel().limit(8) 并行累加 8 个分支 nextLong 之和 = -5844133271374730121
-- ArbitrarilyJumpableUniformRandomProvider(PHILOX_4X64, seed=42) --
PHILOX_4X64 jump(2^40) 副本首个 nextLong = -1665025983784620540
PHILOX_4X64 jumpPowerOfTwo(20) 副本首个 nextLong = -4083233062234229705
-- UniformRandomProvider 流式默认方法(SPLIT_MIX_64, seed=42) --
doubles().limit(5) = [0.7416, 0.1599, 0.2786, 0.3442, 0.0380]
ints(1, 7).limit(10) 模拟掷骰子 = [2, 6, 2, 2, 4, 5, 1, 3, 1, 4]
逐组解读:
- 能力探测:与第四章全量清单一致——Xoshiro 族可跳不可分裂,LXM 族两者兼备,Philox 独占任意距离跳变。选型即选接口:要并行分裂就锁定 LXM 族。
- jump 语义:
jump()不推进原生成器(原实例第 1/2 个值 204392214/360188718 不受影响),而是返回一个「快进到序列远端」的副本;jumps(3)返回 3 个依次快进的副本流,longJump()跳得更远(2^128 级)。跳变值之间序列不重叠,适合固定 worker 数的并行。 - split 语义:
split()从当前实例派生一个全新独立实例,splits(4)产 4 个分支且首值互不相同(实测true);splits()返回无限流,配合parallel()天然适配 ForkJoin 并行——实测把 8 个分支的 nextLong 并行求和得到汇总值。分裂不保证序列「数学上不重叠」,但 LXM 算法把跳跃功能融入状态派生,实战中以 split 为并行首选。 - 任意跳变:
jump(2^40)、jumpPowerOfTwo(2^20)可精确指定跳变距离,Philox 是计数器式结构(状态 = key + counter),因此能「寻址」到序列任意位置,适合把超大模拟按块分给集群节点。 - 流式取数:
doubles()、ints(origin, bound)直接产流,ints(1, 7)模拟掷骰子得到 1 至 6 的整数流,配合limit/boxed/collect一行完成批量取数。
💡 技术拓展:并行随机数策略对比——
| 策略 | 接口 | 适用算法 | 特点 |
|---|---|---|---|
| 跳变 jump | Jumpable/LongJumpable | Xoshiro、LXM、Philox | 同一序列分块,块内不重叠,块大小固定 |
| 分裂 split | Splittable | LXM 族 | 树状派生,天然适配 work-stealing 与流式并行 |
| 线程本地 | ThreadLocalRandomSource | 全部 57 种 | 每线程一个实例自动管理,适合线程池复用 |
本文实测采用:能力探测与 jump 用 XO_SHI_RO_256_SS,split 并行用 L64_X256_MIX,任意跳变用 PHILOX_4X64,线程本地见姊妹篇最佳实践篇。
验证点:jumps(3) 三值互不相同、splits(4) 首值互不相同(实测 true)、并行流求和正常返回,三种并行路径全部可用。
八、状态保存与恢复
RestorableUniformRandomProvider(所有实现均支持)可在任意时刻保存完整内部状态并回滚,实现「随机事件回放」。state 分组实测(实时回显):
java -Dfile.encoding=UTF-8 -cp $CP RngDemo state
== RestorableUniformRandomProvider 状态保存与恢复(MT, seed=555) ==
saveState 后生成 3 个: 328386410, -997242394, 1505232987
restoreState 后生成 3 个: 328386410, -997242394, 1505232987
回滚后序列一致 = true
使用要点:saveState() 返回的 RandomProviderState 句柄要由同一个生成器实例调用 restoreState() 恢复;相比「记住种子重建实例」,状态恢复还能保留「已消费 N 步」的精确位置——回放模拟到第 k 步做分支探索(如蒙特卡洛树搜索)时这是刚需。注意状态对象不透明、不可跨实现使用,且生成器非线程安全,保存/恢复需在同一线程完成。
验证点:回滚后重新生成的 3 个值与回滚前完全一致(true)。
九、参考链接
- UniformRandomProvider Javadoc(1.7):https://commons.apache.org/proper/commons-rng/javadocs/api-1.7/org/apache/commons/rng/UniformRandomProvider.html
- RandomSource Javadoc(1.7):https://commons.apache.org/proper/commons-rng/javadocs/api-1.7/org/apache/commons/rng/simple/RandomSource.html
- client-api 模块全部接口:https://commons.apache.org/proper/commons-rng/javadocs/api-1.7/org/apache/commons/rng/package-summary.html
- Commons RNG 用户指南:https://commons.apache.org/proper/commons-rng/userguide/rng-userguide.html
- 本专栏姊妹篇:《Apache Commons RNG 1.7 快速入门与集成示例》《Apache Commons RNG 1.7 随机数生成示例与最佳实践详解》