如何快速生成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
相关产品推荐
相关产品推荐

