以前在 Java 里生成随机数,鹏磊最烦的就是只能用 Random 或者 SecureRandom,算法固定,性能也不太行。JDK 17 的增强伪随机数生成器(Enhanced Pseudorandom Number Generator)终于解决了这个问题,提供了统一的接口和多种实现,让你能根据需求选择合适的算法,性能也提升了不少。
增强的伪随机数生成器是 JEP 356 引入的特性,提供了 RandomGenerator 接口和多个实现类,支持不同的随机数生成算法。这玩意儿让你能根据需求选择合适的算法,比如需要高性能的用 L32X64MixRandom,需要高质量随机性的用 Xoshiro256PlusPlus,需要可分割的用 SplittableRandom。
为什么需要增强的随机数生成器
以前用 Random 生成随机数,有几个问题:
- 算法固定:只能用线性同余生成器(LCG),质量一般
- 性能一般:同步开销大,多线程性能差
- 功能有限:不支持流式生成,不支持分割
- 扩展性差:不能替换算法实现
增强的随机数生成器解决了这些问题,提供了统一的接口和多种实现。
RandomGenerator 接口
RandomGenerator 是新的统一接口,所有随机数生成器都实现它:
import java.util.random.*;
// 获取默认的随机数生成器
RandomGenerator generator = RandomGenerator.getDefault(); // 获取默认生成器
// 生成随机数
int randomInt = generator.nextInt(100); // 生成 0-99 的随机整数
long randomLong = generator.nextLong(); // 生成随机 long
double randomDouble = generator.nextDouble(); // 生成 0.0-1.0 的随机浮点数
boolean randomBoolean = generator.nextBoolean(); // 生成随机布尔值
// 生成字节数组
byte[] randomBytes = new byte[16]; // 创建字节数组
generator.nextBytes(randomBytes); // 填充随机字节
System.out.println("随机整数: " + randomInt); // 输出随机整数
System.out.println("随机 long: " + randomLong); // 输出随机 long
System.out.println("随机浮点数: " + randomDouble); // 输出随机浮点数
System.out.println("随机布尔值: " + randomBoolean); // 输出随机布尔值
RandomGenerator 接口提供了统一的 API,用起来简单。
不同的实现类
增强的随机数生成器提供了多种实现,每种有不同的特点:
L32X64MixRandom
L32X64MixRandom 是高性能的生成器,适合大多数场景:
import java.util.random.*;
// 创建 L32X64MixRandom 生成器
RandomGenerator generator = RandomGenerator.of("L32X64MixRandom"); // 创建生成器
// 生成随机数
for (int i = 0; i < 10; i++) {
int value = generator.nextInt(100); // 生成 0-99 的随机整数
System.out.print(value + " "); // 输出随机数
}
L32X64MixRandom 性能好,适合大多数场景。
Xoshiro256PlusPlus
Xoshiro256PlusPlus 是高质量的生成器,适合需要高质量随机性的场景:
import java.util.random.*;
// 创建 Xoshiro256PlusPlus 生成器
RandomGenerator generator = RandomGenerator.of("Xoshiro256PlusPlus"); // 创建生成器
// 生成随机数
for (int i = 0; i < 10; i++) {
double value = generator.nextDouble(); // 生成随机浮点数
System.out.print(value + " "); // 输出随机数
}
Xoshiro256PlusPlus 质量高,适合需要高质量随机性的场景。
SplittableRandom
SplittableRandom 是可分割的生成器,适合并行计算:
import java.util.random.*;
// 创建 SplittableRandom 生成器
SplittableRandom random = new SplittableRandom(); // 创建生成器
// 分割生成器(用于并行计算)
SplittableRandom random1 = random.split(); // 分割生成器 1
SplittableRandom random2 = random.split(); // 分割生成器 2
// 在不同线程使用
new Thread(() -> {
for (int i = 0; i < 5; i++) {
int value = random1.nextInt(100); // 使用生成器 1
System.out.println("线程1: " + value); // 输出随机数
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
int value = random2.nextInt(100); // 使用生成器 2
System.out.println("线程2: " + value); // 输出随机数
}
}).start();
SplittableRandom 可分割,适合并行计算,每个线程用独立的生成器。
流式生成
流式生成是增强随机数生成器的一个亮点,能生成随机数流:
import java.util.random.*;
import java.util.stream.*;
// 创建生成器
RandomGenerator generator = RandomGenerator.getDefault(); // 获取默认生成器
// 生成无限流
IntStream intStream = generator.ints(); // 生成无限整数流
intStream.limit(10).forEach(System.out::println); // 输出前 10 个
// 生成有限流
IntStream limitedStream = generator.ints(10); // 生成 10 个随机整数
limitedStream.forEach(System.out::println); // 输出所有
// 生成指定范围的流
IntStream rangeStream = generator.ints(10, 0, 100); // 生成 10 个 0-99 的随机整数
rangeStream.forEach(value -> System.out.print(value + " ")); // 输出所有
// 生成浮点数流
DoubleStream doubleStream = generator.doubles(10, 0.0, 1.0); // 生成 10 个 0.0-1.0 的随机浮点数
doubleStream.forEach(value -> System.out.print(value + " ")); // 输出所有
// 生成 long 流
LongStream longStream = generator.longs(10, 0L, 1000L); // 生成 10 个 0-999 的随机 long
longStream.forEach(value -> System.out.print(value + " ")); // 输出所有
流式生成用起来方便,配合 Stream API 处理随机数特别爽。
RandomGeneratorFactory
RandomGeneratorFactory 能创建和管理随机数生成器:
import java.util.random.*;
// 获取默认工厂
RandomGeneratorFactory<RandomGenerator> factory = RandomGeneratorFactory.getDefault(); // 获取默认工厂
// 创建生成器
RandomGenerator generator = factory.create(); // 使用默认种子创建
RandomGenerator seededGenerator = factory.create(12345L); // 使用指定种子创建
// 获取所有可用的工厂
RandomGeneratorFactory.all() // 获取所有工厂
.forEach(f -> {
System.out.println("名称: " + f.name()); // 输出名称
System.out.println("组: " + f.group()); // 输出组
System.out.println("状态位数: " + f.stateBits()); // 输出状态位数
System.out.println("周期: " + f.period()); // 输出周期
System.out.println("---"); // 分隔符
});
// 根据名称创建生成器
RandomGeneratorFactory<RandomGenerator> l32x64Factory = RandomGeneratorFactory.of("L32X64MixRandom"); // 获取工厂
RandomGenerator l32x64Generator = l32x64Factory.create(); // 创建生成器
RandomGeneratorFactory 能查询生成器的属性,选择合适的实现。
实际应用场景
场景一:游戏开发
游戏开发需要大量随机数,增强的随机数生成器特别适合:
import java.util.random.*;
// 游戏随机数生成器
public class GameRandom {
private final RandomGenerator generator; // 随机数生成器
public GameRandom() {
// 使用高性能生成器
this.generator = RandomGenerator.of("L32X64MixRandom"); // 创建生成器
}
// 生成随机伤害
public int randomDamage(int min, int max) {
return generator.nextInt(min, max + 1); // 生成 min-max 的随机伤害
}
// 生成随机掉落
public boolean randomDrop(double chance) {
return generator.nextDouble() < chance; // 根据概率判断是否掉落
}
// 生成随机位置
public double[] randomPosition(double minX, double maxX, double minY, double maxY) {
double x = generator.nextDouble(minX, maxX); // 随机 X 坐标
double y = generator.nextDouble(minY, maxY); // 随机 Y 坐标
return new double[]{x, y}; // 返回位置
}
}
// 使用示例
GameRandom gameRandom = new GameRandom(); // 创建游戏随机数生成器
// 生成随机伤害
int damage = gameRandom.randomDamage(10, 20); // 生成 10-20 的随机伤害
System.out.println("伤害: " + damage); // 输出伤害
// 生成随机掉落
boolean dropped = gameRandom.randomDrop(0.3); // 30% 概率掉落
System.out.println("掉落: " + dropped); // 输出是否掉落
// 生成随机位置
double[] position = gameRandom.randomPosition(0.0, 100.0, 0.0, 100.0); // 生成随机位置
System.out.println("位置: (" + position[0] + ", " + position[1] + ")"); // 输出位置
游戏开发用增强的随机数生成器,性能好,功能强。
场景二:蒙特卡洛模拟
蒙特卡洛模拟需要大量随机数,流式生成特别适合:
import java.util.random.*;
import java.util.stream.*;
// 蒙特卡洛模拟:计算 π
public class MonteCarloPi {
private final RandomGenerator generator; // 随机数生成器
public MonteCarloPi() {
this.generator = RandomGenerator.getDefault(); // 获取默认生成器
}
// 计算 π(使用蒙特卡洛方法)
public double estimatePi(long iterations) {
// 生成随机点流
long insideCircle = generator.doubles(iterations) // 生成 X 坐标流
.mapToObj(x -> {
double y = generator.nextDouble(); // 生成 Y 坐标
return x * x + y * y <= 1.0; // 判断是否在圆内
})
.filter(b -> b) // 过滤在圆内的点
.count(); // 统计数量
return 4.0 * insideCircle / iterations; // 计算 π 的估计值
}
}
// 使用示例
MonteCarloPi mc = new MonteCarloPi(); // 创建蒙特卡洛模拟器
double pi = mc.estimatePi(1_000_000); // 使用 100 万次迭代估计 π
System.out.println("π 的估计值: " + pi); // 输出估计值
蒙特卡洛模拟用流式生成,代码简洁,性能也好。
场景三:数据采样
数据采样是常见需求,流式生成特别适合:
import java.util.random.*;
import java.util.stream.*;
import java.util.*;
// 数据采样器
public class DataSampler {
private final RandomGenerator generator; // 随机数生成器
public DataSampler() {
this.generator = RandomGenerator.getDefault(); // 获取默认生成器
}
// 随机采样
public <T> List<T> sample(List<T> data, int size) {
// 生成随机索引流
return generator.ints(size, 0, data.size()) // 生成随机索引
.mapToObj(data::get) // 获取对应元素
.collect(Collectors.toList()); // 收集为列表
}
// 加权采样
public <T> T weightedSample(Map<T, Double> weights) {
double totalWeight = weights.values().stream() // 计算总权重
.mapToDouble(Double::doubleValue)
.sum();
double random = generator.nextDouble() * totalWeight; // 生成随机值
double cumulative = 0.0; // 累积权重
for (var entry : weights.entrySet()) {
cumulative += entry.getValue(); // 累加权重
if (random <= cumulative) {
return entry.getKey(); // 返回对应元素
}
}
return weights.keySet().iterator().next(); // 返回第一个(不应该到这里)
}
}
// 使用示例
DataSampler sampler = new DataSampler(); // 创建采样器
// 随机采样
List<String> data = Arrays.asList("A", "B", "C", "D", "E"); // 数据列表
List<String> sample = sampler.sample(data, 3); // 随机采样 3 个
System.out.println("采样结果: " + sample); // 输出采样结果
// 加权采样
Map<String, Double> weights = new HashMap<>(); // 权重映射
weights.put("A", 0.5); // A 的权重 0.5
weights.put("B", 0.3); // B 的权重 0.3
weights.put("C", 0.2); // C 的权重 0.2
String weighted = sampler.weightedSample(weights); // 加权采样
System.out.println("加权采样: " + weighted); // 输出采样结果
数据采样用流式生成,代码简洁,功能强大。
性能优化
增强的随机数生成器性能优化明显:
import java.util.random.*;
import java.util.*;
// 性能对比
public class RandomPerformance {
// 传统 Random
public static void testOldRandom(int iterations) {
Random random = new Random(); // 创建传统 Random
long start = System.nanoTime(); // 开始时间
for (int i = 0; i < iterations; i++) {
random.nextInt(); // 生成随机数
}
long time = System.nanoTime() - start; // 计算时间
System.out.println("传统 Random: " + time / 1_000_000 + " ms"); // 输出时间
}
// 增强的随机数生成器
public static void testNewRandom(int iterations) {
RandomGenerator generator = RandomGenerator.of("L32X64MixRandom"); // 创建生成器
long start = System.nanoTime(); // 开始时间
for (int i = 0; i < iterations; i++) {
generator.nextInt(); // 生成随机数
}
long time = System.nanoTime() - start; // 计算时间
System.out.println("增强生成器: " + time / 1_000_000 + " ms"); // 输出时间
}
// 流式生成
public static void testStreamRandom(int iterations) {
RandomGenerator generator = RandomGenerator.getDefault(); // 获取默认生成器
long start = System.nanoTime(); // 开始时间
generator.ints(iterations).sum(); // 流式生成并求和
long time = System.nanoTime() - start; // 计算时间
System.out.println("流式生成: " + time / 1_000_000 + " ms"); // 输出时间
}
}
// 性能测试
int iterations = 10_000_000; // 1000 万次迭代
RandomPerformance.testOldRandom(iterations); // 测试传统 Random
RandomPerformance.testNewRandom(iterations); // 测试增强生成器
RandomPerformance.testStreamRandom(iterations); // 测试流式生成
增强的随机数生成器性能提升明显,特别是流式生成的时候。
注意事项和最佳实践
注意事项
-
线程安全:大多数实现不是线程安全的,多线程使用需要同步或者用
SplittableRandom。 -
种子管理:使用相同种子会生成相同的序列,需要随机性时不要用固定种子。
-
算法选择:根据需求选择合适的算法,性能和质量要平衡。
-
流式生成:流式生成适合批量操作,单个生成用普通方法就行。
最佳实践
-
选择合适的算法:根据需求选择合适的算法,性能和质量要平衡。
-
使用流式生成:批量生成随机数时用流式生成,性能更好。
-
多线程使用 SplittableRandom:多线程场景用
SplittableRandom,每个线程用独立的生成器。 -
避免固定种子:需要随机性时不要用固定种子,让系统自动生成。
-
文档说明:在代码注释中说明为什么选择某个算法,帮助其他开发者理解。
总结
增强的伪随机数生成器是 JDK 17 的一个实用特性,提供了统一的接口和多种实现,让你能根据需求选择合适的算法。流式生成特别方便,配合 Stream API 处理随机数特别爽。
建议在实际项目中试试,特别是需要大量随机数的场景。下一篇文章咱就聊聊多线程环境下的随机数生成,看看怎么在并发场景下安全高效地生成随机数。兄弟们有啥问题随时问,鹏磊会尽量解答。