HashSet是一个用于存储惟一元素的集合。java
在本文中,咱们将讨论java.util.HashSet 类中removeAll()方法 的性能。git
HashSet 的 removeAll 方法删除全部包含指定集合的元素:github
Set<Integer> set = new HashSet<Integer>(); set.add(1); set.add(2); set.add(3); set.add(4); Collection<Integer> collection = new ArrayList<Integer>(); collection.add(1); collection.add(3); set.removeAll(collection); Integer[] actualElements = new Integer[set.size()]; Integer[] expectedElements = new Integer[] { 2, 4 }; assertArrayEquals(expectedElements, set.toArray(actualElements));
结果,原集合里的元素 1 和 3 将被中删除。性能
removeAll()方法会先肯定哪一个集合更小,集合大小不一样,执行逻辑不一样。这是经过在原集合和指定集合上调用size() 方法来完成的。测试
若是指定集合的元素少于指定原的元素,则它以时间复杂度O( n )迭代指定的集合。它还检查元素是否存在于时间复杂度为 O(1) 的集合中。若是元素存在,则使用集合的remove()方法将其从原集合中 删除,该方法的时间复杂度为 O(1)。因此总的时间复杂度是 O( n )。code
若是原集合的元素少于指定集合,则它使用 O( n )迭代此原集合。而后它经过调用其contains()方法检查指定集合中是否存在每一个元素。若是存在这样的元素,则从原集合中删除该元素。因此这取决于contains()方法的时间复杂度。orm
如今在这种状况下,若是指定集合是一个ArrayList,contains()方法的时间复杂度是 O( m )。所以,从集合HashSet中删除ArrayList 中存在的全部元素的整体时间复杂度为 O( n * m )。rem
若是指定集合再次是HashSet,则contains()方法的时间复杂度为 O(1)。所以,从集合HashSet中删除HashSet 中存在的全部元素的整体时间复杂度为 O( n )。get
为了查看以上3种状况的性能差别,咱们来编写一个简单的JMH基准测试。hash
对于第一种状况,咱们将初始化原集合和指定集合,其中原集合中的元素多于指定集合。在第二种状况下,指定集合中的元素多于原集合。在第三种状况下,第二个集合的元素数量比第一个集合多:
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5) public class HashSetBenchmark { @State(Scope.Thread) public static class MyState { private Set employeeSet1 = new HashSet<>(); private List employeeList1 = new ArrayList<>(); private Set employeeSet2 = new HashSet<>(); private List employeeList2 = new ArrayList<>(); private Set<Employee> employeeSet3 = new HashSet<>(); private Set<Employee> employeeSet4 = new HashSet<>(); private long set1Size = 60000; private long list1Size = 50000; private long set2Size = 50000; private long list2Size = 60000; private long set3Size = 50000; private long set4Size = 60000; @Setup(Level.Trial) public void setUp() { // populating sets } } }
以后,咱们添加咱们的基准测试:
@Benchmark public boolean given_SizeOfHashsetGreaterThanSizeOfCollection_whenRemoveAllFromHashSet_thenGoodPerformance(MyState state) { return state.employeeSet1.removeAll(state.employeeList1); } @Benchmark public boolean given_SizeOfHashsetSmallerThanSizeOfCollection_whenRemoveAllFromHashSet_thenBadPerformance(MyState state) { return state.employeeSet2.removeAll(state.employeeList2); } @Benchmark public boolean given_SizeOfHashsetSmallerThanSizeOfAnotherHashSet_whenRemoveAllFromHashSet_thenGoodPerformance(MyState state) { return state.employeeSet3.removeAll(state.employeeSet4); }
结果以下:
Benchmark Mode Cnt Score Error Units HashSetBenchmark.testHashSetSizeGreaterThanCollection avgt 20 2700457.099 ± 475673.379 ns/op HashSetBenchmark.testHashSetSmallerThanCollection avgt 20 31522676649.950 ± 3556834894.168 ns/op HashSetBenchmark.testHashSetSmallerThanOtherHashset avgt 20 2672757.784 ± 224505.866 ns/op
咱们能够看到当HashSet的元素少于Collection 时,HashSet.removeAll() 的表现很是糟糕,Collection做为参数传递给removeAll()方法。可是当另外一个集合再次是HashSet 时,则性能很好。
在本文中,咱们看到了HashSet中removeAll()的性能。当原集合的元素少于集合的元素时,removeAll()的性能取决于集合的contains()方法的时间复杂度。
最后,本文的完整代码可 在 GitHub 上找到。