Java(7,8)中用于消除一个integer
的{{1}}个元素的最佳性能方法是什么?所有元素在第一和第二个列表中都是唯一的。
目前我知道API方法Arraylist
并以这种方式使用它:
removeall
当我使用arraylists进行操作时,会出现问题超过10000个元素。例如,当我删除65000个元素时,延迟似乎约为2秒。但我需要使用超过1000000个元素的更大的列表进行操作。
这个问题的策略是什么?
使用新的Stream API可能会解决它吗?
答案 0 :(得分:15)
<强> TL; DR:强>
保持简单。使用
list.removeAll(new HashSet<T>(listOfElementsToRemove));
代替。
正如Eran在his answer中已经提到的那样:性能低下的原因是通用removeAll
实现的伪代码
public boolean removeAll(Collection<?> c) {
for (each element e of this) {
if (c.contains(e)) {
this.remove(e);
}
}
}
因此,对要删除的元素列表执行的contains
调用将导致O(n * k)性能(其中n
是要删除的元素数,{{1调用方法的列表中元素的数量)。
天真地,人们可以想象k
上的this.remove(e)
调用也可能有O(k),这种实现也会有二次复杂性。但情况并非如此:您提到列表是专门的List
个实例。并且ArrayList
方法被实现为委托给名为ArrayList#removeAll
的方法,该方法直接在底层数组上运行,不单独删除元素。
所以你要做的就是确保包含要删除的元素的集合中的查找很快 - 最好是O(1)。这可以通过将这些元素放入batchRemove
来实现。最后,它可以写成
Set
附注:
Eran的回答有恕我直言的两个主要缺点:首先,它需要排序列表,即O(n * logn) - 并且它根本就没有必要。但更重要的是(显然):排序可能会改变元素的顺序!如果根本不需要这会怎么样?
远程相关:list.removeAll(new HashSet<T>(listOfElementsToRemove));
实施中还涉及其他一些细微之处。例如,在某些情况下HashSet removeAll method is surprisingly slow。虽然当要删除的元素存储在列表中时,这也归结为O(n * n),但在这种特殊情况下,确切的行为可能确实令人惊讶。
答案 1 :(得分:10)
好吧,因为removeAll
检查tempList
的每个元素是否出现在tempList2
中,所以运行时间与第一个列表的大小相乘,乘以第二个列表的大小list,表示O(N^2)
,除非两个列表中的一个非常小并且可以被视为“常量”。
另一方面,如果您对列表进行预排序,然后使用单次迭代迭代两个列表(类似于合并排序中的合并步骤),则排序将采用O(NlogN)
和迭代O(N)
,总运行时间为O(NlogN)
。这里N
是两个列表中较大者的大小。
如果您可以按排序结构替换列表(可能是TreeSet
,因为您说元素是唯一的),您可以在线性时间内实现removeAll
,因为您不必做任何排序。
我还没有对它进行测试,但是这样的事情可以起作用(假设tempList
和tempList2
都已排序):
Iterator<Integer> iter1 = tempList.iterator();
Iterator<Integer> iter2 = tempList2.iterator();
Integer current = null;
Integer current2 = null;
boolean advance = true;
while (iter1.hasNext() && iter2.hasNext()) {
if (advance) {
current = iter1.next();
advance = false;
}
if (current2 == null || current > current2) {
current2 = iter2.next();
}
if (current <= current2) {
advance = true;
if (current == current2)
iter1.remove();
}
}
答案 2 :(得分:2)
我怀疑从ArrayList中删除是一种性能命中,因为当删除中间的元素时,列表可能被分割,或者在删除元素后必须压缩列表。这样做可能会更快:
这应该有O(N)
;如果创建Set并且其中的查找被假定为常量。