Apache Commons RNG 1.7 常用 API 与随机源详解

详解 Apache Commons RNG 1.7 的常用 API 与随机源体系,含 57 个 RandomSource 枚举全量清单与算法族分类、UniformRandomProvider 全套取数方法、种子创建与可复现性实测、可跳可分裂可流式 provider 与状态保存恢复,全部附真实运行回显。

Apache Commons RNGApache Commons RNG2026/10/15

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(枚举)simple57 种算法的工厂:create/createLong/createInt第四章
UniformRandomProviderclient-api基础取数:nextInt/nextLong/nextDouble/nextFloat/nextBoolean/nextBytes + 流式默认方法第五章
RestorableUniformRandomProviderclient-api状态保存与恢复(saveState/restoreState)第八章
JumpableUniformRandomProviderclient-api生成远距离快进副本(jump/jumps/longJump)第七章
LongJumpableUniformRandomProviderclient-api生成超远距离(2^128 级)快进副本第七章
ArbitrarilyJumpableUniformRandomProviderclient-api按任意距离跳变(jump(distance)/jumpPowerOfTwo)第七章
SplittableUniformRandomProviderclient-api分裂出新独立实例(split/splits),并行流首选第七章
ThreadLocalRandomSourcesimple每线程自动持有独立生成器第七章
RandomProviderStateclient-apisaveState 返回的状态句柄第八章
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 一行完成批量取数。

💡 技术拓展:并行随机数策略对比——

策略接口适用算法特点
跳变 jumpJumpable/LongJumpableXoshiro、LXM、Philox同一序列分块,块内不重叠,块大小固定
分裂 splitSplittableLXM 族树状派生,天然适配 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)。

九、参考链接

  1. UniformRandomProvider Javadoc(1.7):https://commons.apache.org/proper/commons-rng/javadocs/api-1.7/org/apache/commons/rng/UniformRandomProvider.html
  2. RandomSource Javadoc(1.7):https://commons.apache.org/proper/commons-rng/javadocs/api-1.7/org/apache/commons/rng/simple/RandomSource.html
  3. client-api 模块全部接口:https://commons.apache.org/proper/commons-rng/javadocs/api-1.7/org/apache/commons/rng/package-summary.html
  4. Commons RNG 用户指南:https://commons.apache.org/proper/commons-rng/userguide/rng-userguide.html
  5. 本专栏姊妹篇:《Apache Commons RNG 1.7 快速入门与集成示例》《Apache Commons RNG 1.7 随机数生成示例与最佳实践详解》