上一篇                     
               
			  Java如何避免随机数重复?
- 后端开发
- 2025-06-20
- 3637
 在Java中,可通过以下方法避免随机数重复:,1. 使用
 
 
Collections.shuffle()打乱有序序列(如1-100),2. 使用
 Set集合存储并校验重复值,3. 使用
 ThreadLocalRandom或
 SecureRandom配合循环检测,4. 利用数据库自增ID或UUID保证唯一性,需根据场景选择合适方案,如抽奖推荐洗牌算法,ID生成推荐UUID。
基于集合的洗牌算法(小范围高效)
原理:预生成所有可能值,随机打乱顺序后顺序取出
适用场景:有限且范围小的数据集(如1-100的抽奖) 
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 100; i++) { // 预填充数据
    numbers.add(i);
}
Collections.shuffle(numbers); // 随机打乱
// 按序取出不重复值
for (int num : numbers) {
    System.out.println(num);
} 
优点:O(n)时间复杂度,绝对不重复
缺点:内存占用高,不适用大范围(如10亿)
HashSet去重(动态生成)
原理:生成随机数后存入Set自动去重
Set<Integer> uniqueSet = new HashSet<>();
Random rand = new Random();
while (uniqueSet.size() < 50) { // 目标50个不重复数
    int num = rand.nextInt(1000); // 范围[0,999]
    uniqueSet.add(num);
} 
注意:

- 集合大小接近范围上限时,性能急剧下降(碰撞概率高)
- 需设置最大循环次数避免死循环
线性同余生成器(LCG)定制
原理:通过数学公式控制随机序列不重复
// 自定义LCG参数 (a, c, m需符合Hull-Dobell定理)
class CustomLCG {
    private long seed;
    private final long a = 1664525, c = 1013904223, m = (long) Math.pow(2, 32);
    public CustomLCG(long seed) {
        this.seed = seed;
    }
    public int nextUnique() {
        seed = (a * seed + c) % m; // 生成下一个数
        return (int) (seed % 10000); // 限制范围
    }
}
// 使用示例
CustomLCG generator = new CustomLCG(System.currentTimeMillis());
Set<Integer> results = new HashSet<>();
while (results.size() < 1000) {
    results.add(generator.nextUnique());
} 
适用场景:需要可预测序列的场景(如游戏关卡生成)
风险:参数选择不当会导致周期缩短
安全随机数(高安全性场景)
原理:用SecureRandom生成密码学强度的随机数 

SecureRandom secureRandom = new SecureRandom();
Set<Integer> secureSet = new HashSet<>();
while (secureSet.size() < 10) {
    int num = secureRandom.nextInt(100_000);
    secureSet.add(num);
} 
优势:抵御预测攻击,适用于令牌、密钥生成
代价:性能比Random低10倍以上
位图法(BitMap)超大范围优化
原理:用比特位标记已生成数字,节省内存
BitSet bitSet = new BitSet(1_000_000); // 100万范围
Random rand = new Random();
int count = 0;
while (count < 10_000) { // 目标1万个不重复数
    int num = rand.nextInt(1_000_000);
    if (!bitSet.get(num)) {
        bitSet.set(num); // 标记已使用
        count++;
    }
} 
优势:内存压缩至传统数组的1/32
适用:范围大但样本稀疏的场景(如百万中取1万)
关键注意事项
- 性能陷阱 HashSet去重在覆盖率>70%时碰撞率飙升,应改用洗牌或BitMap  
- 随机性质量 
  - 普通Random类周期为2⁴⁸,优先用ThreadLocalRandom(Java7+)
 
- 普通
- 并发场景 
  - 多线程下使用SplittableRandom(Java8+)避免竞争
 
- 多线程下使用
- 分布式系统 需结合数据库唯一约束或Snowflake算法,单机随机无法保证全局唯一 
方法选型建议
| 场景 | 推荐方法 | 
|---|---|
| 小范围全量采样(<10⁶) | 洗牌算法(Collections.shuffle) | 
| 大范围稀疏采样 | BitMap位图法 | 
| 安全敏感场景 | SecureRandom + HashSet | 
| 可预测序列需求 | 自定义LCG | 
引用说明:
- LCG参数选择参考《计算机程序设计艺术》卷2(Donald Knuth)
- 安全随机数标准遵循NIST SP 800-90A Rev.1
- 并发随机数生成器设计基于Java官方文档
通过科学选择算法,可平衡性能、内存与安全性需求,实际开发中需严格测试边界条件(如范围溢出、线程竞争),避免业务逻辑破绽。
 
  
			 
			 
			 
			 
			 
			