【发布时间】:2015-04-24 15:58:33
【问题描述】:
Jon Skeet 最近在他的博客上提出了一个有趣的编程话题:"There's a hole in my abstraction, dear Liza, dear Liza"(添加了重点):
我有一套——事实上是
HashSet。我想从中删除一些项目……而且许多项目可能不存在。事实上,在我们的测试用例中,“removals”集合中的 none 项将在原始集合中。这听起来 - 确实是 - 非常容易编码。毕竟,我们有Set<T>.removeAll来帮助我们,对吧?我们在命令行上指定“源”集的大小和“移除”集合的大小,并构建它们。源集只包含非负整数;移除集仅包含负整数。我们使用
System.currentTimeMillis()测量删除所有元素所需的时间,这不是世界上最准确的秒表,但在这种情况下已经绰绰有余,正如您将看到的那样。代码如下:import java.util.*; public class Test { public static void main(String[] args) { int sourceSize = Integer.parseInt(args[0]); int removalsSize = Integer.parseInt(args[1]); Set<Integer> source = new HashSet<Integer>(); Collection<Integer> removals = new ArrayList<Integer>(); for (int i = 0; i < sourceSize; i++) { source.add(i); } for (int i = 1; i <= removalsSize; i++) { removals.add(-i); } long start = System.currentTimeMillis(); source.removeAll(removals); long end = System.currentTimeMillis(); System.out.println("Time taken: " + (end - start) + "ms"); } }让我们从简单的工作开始:包含 100 个项目的源集,还有 100 个要删除:
c:UsersJonTest>java Test 100 100 Time taken: 1ms好的,所以我们没想到它会很慢......显然我们可以稍微提高一点。 100 万个项目和 300,000 个要删除的项目的来源怎么样?
c:UsersJonTest>java Test 1000000 300000 Time taken: 38ms嗯。这看起来还是挺快的。现在我觉得我有点残忍,要求它做所有的移除。让我们让它变得更简单一些——300,000 个源项目和 300,000 个移除:
c:UsersJonTest>java Test 300000 300000 Time taken: 178131ms对不起?将近三 分钟?哎呀!与我们在 38 毫秒内管理的集合相比,从 更小的 集合中删除项目确实应该更容易?
有人可以解释为什么会这样吗?为什么HashSet<T>.removeAll 方法这么慢?
【问题讨论】:
-
我测试了你的代码,它运行得很快。对于您的情况,完成大约需要 12 毫秒。我还将两个输入值都增加了 10,它花了 36 毫秒。也许您的 PC 在您运行测试时会执行一些密集的 CPU 任务?
-
我测试了它,结果与 OP 相同(好吧,我在结束前停止了它)。确实很奇怪。视窗,JDK 1.7.0_55
-
有一张公开的票:JDK-6982173
-
作为discussed on Meta,这个问题最初是从 Jon Skeet 的博客中抄袭的(由于版主的编辑,现在直接引用并链接到问题中)。未来的读者应该注意,它被剽窃的博客文章实际上解释了行为的原因,类似于这里接受的答案。因此,您可能不想在这里阅读答案,而是希望简单地点击并阅读the full blog post。
-
该错误将在 Java 15 中修复:JDK-6394757
标签: java performance collections hashset