Java快速统计List中包含的三元组数量——性能优化及多线程咨询
优化方案:从数据结构到多线程的全流程优化
1. 单线程核心优化:用高效数据结构替代线性查找
你的三重循环最大的问题是内层遍历整个数字组查找元素,时间复杂度是O(M3N)(M是三元组数量,N是数字组长度)。换成以下两种数据结构,能把单个元素的查找时间从O(N)降到O(1):
方案1:HashSet(通用场景)
把数字组转换成HashSet,直接判断三元组的三个元素是否都在集合中:
Set<Integer> zahlenSet = new HashSet<>(zahlen); int treffer = 0; for(List<Integer> dreier : dreierKombis) { if(zahlenSet.contains(dreier.get(0)) && zahlenSet.contains(dreier.get(1)) && zahlenSet.contains(dreier.get(2))) { treffer++; } }
这个修改能把单三元组的检查时间从O(N)降到O(1),对于你的场景,效率至少提升6倍(因为数字组长度是6)。
方案2:BitSet(针对1-42的乐透场景)
由于乐透数字范围固定是1-42,BitSet是更高效的选择——占用空间极小(仅42位=5字节),且检查操作是原生位运算,速度比HashSet更快:
BitSet zahlenBitSet = new BitSet(43); // 数字范围1-42,索引从1开始 for(int num : zahlen) { zahlenBitSet.set(num); } int treffer = 0; for(List<Integer> dreier : dreierKombis) { if(zahlenBitSet.get(dreier.get(0)) && zahlenBitSet.get(dreier.get(1)) && zahlenBitSet.get(dreier.get(2))) { treffer++; } }
这种方式比HashSet的效率还要高30%-50%,非常适配你的乐透数字范围。
2. 多线程优化:拆分任务并行处理
当单线程优化后仍不够时,可以通过多线程利用CPU多核资源,核心是任务拆分:
拆分方式选择
根据你的数据规模(1.5亿三元组 + 1000个数字组),有两种拆分思路:
- 按数字组分拆:每个线程处理一个或多个数字组,对所有1.5亿三元组进行检查。适合数字组数量较多的场景。
- 按三元组分拆:把1.5亿三元组分成若干块,每个线程处理一块三元组,对所有1000个数字组进行检查。适合三元组数量极大的场景。
示例代码(按数字组分拆的线程池实现)
// 初始化线程池,核心线程数设为CPU核心数(比如8核就设8) ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); List<Future<Integer>> futures = new ArrayList<>(); // 预处理所有数字组为BitSet List<BitSet> zahlenBitSets = new ArrayList<>(); for(List<Integer> zahlen : alleZahlenGruppen) { // alleZahlenGruppen是你的1000个数字组 BitSet bs = new BitSet(43); for(int num : zahlen) bs.set(num); zahlenBitSets.add(bs); } // 提交任务:每个线程处理一个数字组的检查 for(BitSet bs : zahlenBitSets) { futures.add(executor.submit(() -> { int count = 0; for(List<Integer> dreier : dreierKombis) { if(bs.get(dreier.get(0)) && bs.get(dreier.get(1)) && bs.get(dreier.get(2))) { count++; } } return count; })); } // 汇总所有结果 int totalTreffer = 0; for(Future<Integer> future : futures) { totalTreffer += future.get(); } executor.shutdown();
注意事项
- 不要手动创建线程,用
ExecutorService线程池管理,避免线程创建销毁的开销。 - 如果三元组数据量太大(1.5亿),可以把三元组分块后存入内存或者磁盘,每个线程读取一块处理,避免内存溢出。
- 乐透场景下,可以提前把每个三元组转换成排序后的整数数组或自定义不可变类,避免重复排序,进一步提升效率。
3. 进阶优化:预处理历史开奖的三元组集合
根据你的核心背景,其实可以换个思路:每个历史开奖号码的三元组只有20个(C(6,3)=20),把所有历史开奖的三元组合并成一个全局HashSet(去重后最多100020=20000个),然后直接统计1.5亿个组合三元组中有多少在这个全局Set里。这种方式的时间复杂度是O(M)(M是三元组数量),比原来的O(MK)(K是数字组数量)效率提升几个数量级:
// 预处理所有历史开奖的三元组,存入全局Set Set<String> historicalTriples = new HashSet<>(); for(List<Integer> ziehung : alleHistorischenZiehungen) { // 所有历史开奖号码 // 生成该开奖号码的所有三元组 List<List<Integer>> triples = generateTriples(ziehung); // 把三元组排序后转成字符串当key(避免顺序不同的重复三元组,比如[1,2,3]和[3,2,1]视为同一个) for(List<Integer> t : triples) { Collections.sort(t); historicalTriples.add(t.toString()); } } // 统计目标三元组中在全局Set里的数量 int treffer = 0; for(List<Integer> dreier : dreierKombis) { Collections.sort(dreier); if(historicalTriples.contains(dreier.toString())) { treffer++; } }
这种方式完全规避了对每个数字组遍历所有三元组的操作,直接把问题转换成集合查找,效率提升非常明显。
内容的提问来源于stack exchange,提问作者Gaukler
相关产品推荐
相关产品推荐

