You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何快速生成1000万个唯一数字?Groovy/Java性能优化求助

优化思路与解决方案

一、核心性能瓶颈分析

原代码效率极低的根本原因:

  • ArrayList.contains()的O(n)复杂度:每次查重都要遍历整个集合,数据量越大耗时呈指数级增长,1000万级数据下完全不可行。
  • 重复创建Random实例:每次调用makeNumber都新建Random,初始化开销持续累加。
  • 频繁同步IO操作:每次添加元素都执行print,逐行调用file.append(),IO速度远低于内存操作,严重拖慢整体流程。
  • 随机生成+查重的低效模式:随着已生成数据量增加,随机碰撞概率上升,后期会出现大量无效尝试。

二、针对性优化方案

1. 替换查重数据结构,降低查重复杂度

用BitSet或HashSet替代ArrayList,将查重操作从O(n)降到O(1):

  • BitSet:内存占用极小(2000万数字仅需2.5MB),适合大范围数字的查重标记。
  • HashSet:实现简单,适合存储字符串或数字对象,内存占用略高于BitSet但开发成本低。

示例(BitSet版):

int total = 10000000
int minNum = 10000000
int maxNum = minNum + 20000000 // 对应原makeNumber的数字范围
BitSet used = new BitSet(maxNum - minNum)
Random random = new Random()
List<String> batch = new ArrayList<>(10000) // 批量攒数据减少IO
BufferedWriter writer = new BufferedWriter(new FileWriter("out.txt"))

int count = 0
while (count < total) {
    int num = minNum + random.nextInt(maxNum - minNum)
    int index = num - minNum
    if (!used.get(index)) {
        used.set(index)
        batch.add("83" + num)
        count++
        // 每攒10000条批量写入磁盘
        if (batch.size() == 10000) {
            batch.each { s ->
                writer.write(s)
                writer.newLine()
            }
            batch.clear()
            // 可选:打印进度,避免频繁输出
            if (count % 1000000 == 0) {
                println "已生成 ${count} 条数据"
            }
        }
    }
}
// 写入剩余数据
batch.each { s ->
    writer.write(s)
    writer.newLine()
}
writer.close()

2. 预先生成无重复序列(洗牌法)

如果允许数字范围为固定池,直接通过洗牌生成无重复序列,彻底避免查重操作:

int total = 10000000
int minNum = 10000000
// 生成1000万到2000万的数字池(刚好1000万条,无需查重)
List<Integer> numPool = (minNum..<minNum+total).collect()
// Fisher-Yates洗牌打乱顺序
Random random = new Random()
for (int i = numPool.size() - 1; i > 0; i--) {
    int j = random.nextInt(i + 1)
    int temp = numPool[i]
    numPool[i] = numPool[j]
    numPool[j] = temp
}
// 批量写入文件
BufferedWriter writer = new BufferedWriter(new FileWriter("out.txt"))
numPool.each { num ->
    writer.write("83" + num)
    writer.newLine()
}
writer.close()

注:若必须覆盖1000万-3000万全范围,可将数字池扩大到2000万条,洗牌后取前1000万即可。

3. 多线程并行生成(利用多核CPU)

拆分数字范围到多个线程,每个线程独立生成无重复数据,最后合并结果:

int total = 10000000
int threadCount = 4 // 根据CPU核心数调整
int perThread = total / threadCount
List<Thread> threads = new ArrayList<>()
List<File> tempFiles = new ArrayList<>()

for (int i = 0; i < threadCount; i++) {
    int start = 10000000 + i * (20000000 / threadCount)
    int end = start + (20000000 / threadCount)
    File tempFile = new File("temp_${i}.txt")
    tempFiles.add(tempFile)
    
    threads.add(new Thread(() -> {
        Random random = new Random()
        BitSet used = new BitSet(end - start)
        BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))
        int count = 0
        while (count < perThread) {
            int num = start + random.nextInt(end - start)
            int index = num - start
            if (!used.get(index)) {
                used.set(index)
                writer.write("83" + num)
                writer.newLine()
                count++
            }
        }
        writer.close()
    }))
}

// 启动并等待所有线程完成
threads.each { it.start() }
threads.each { it.join() }

// 合并临时文件到最终文件
BufferedWriter finalWriter = new BufferedWriter(new FileWriter("out.txt"))
tempFiles.each { file ->
    file.eachLine { line ->
        finalWriter.write(line)
        finalWriter.newLine()
    }
    file.delete() // 删除临时文件
}
finalWriter.close()

4. 其他细节优化

  • 复用Random实例:避免每次创建新Random,多线程场景下优先用ThreadLocalRandom,比Random更高效。
  • 移除不必要的print:仅保留关键进度打印,避免频繁IO拖慢速度。
  • 优先使用Random而非SecureRandom:SecureRandom为加密级随机,速度远慢于Random,若无加密安全需求无需使用。
  • 批量写入文件:用BufferedWriter替代File.append(),减少磁盘IO次数。

三、性能对比

原代码生成100万条耗时5小时,优化后生成1000万条可在几分钟内完成(具体耗时取决于CPU性能和磁盘读写速度)。

内容的提问来源于stack exchange,提问作者imp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:44:50