让我们假设我调用第三方API并获取可变的N-many对象列表。此列表可以小到10个对象,也可以大到几千个。然后我总是想在返回的List的索引0处只插入一个对象。我知道我可以轻松地在索引0处调用add,但这将是O(n),因为它会移动插入的每个对象。我的问题是,平均(处理方式)是否更快创建一个新的List,其中包含我计划在开头插入的项目,然后在新的List上调用addAll传递返回的第三方N-many列表?
答案 0 :(得分:3)
这取决于列表实现。如果你真的不知道你的第三方给你的列表实现,你所能做的只是经验测试和基准测试。
更有可能的是,他们会给你一个标准的Java列表类型,确实你已经标记了你的问题arraylist
- 这就是你给出的那个吗?
ArrayList.add(index,element)
使用System.arrayCopy()
将每个移位的元素从索引n
复制到n+1
,然后将新元素写入其插槽。这是O(n),但它确实可能非常快,因为它将使用高度优化的系统memmove
例程一次移动整个RAM块。 (见Why is memcpy() and memmove() faster than pointer increments?)。
此外,如果您的额外元素超出了列表大小超过分配的后备数组的大小,Java将创建一个新数组并将arraycopy
整个批次放入其中。
请记住,您只是复制对象引用,而不是整个对象,因此对于1000个元素,您正在复制(最坏情况是在64位计算机上)64位* 1000 == 8 KB的RAM。
但是,对于非常庞大的列表,它所花费的时间可能会变得非常重要。插入链表更便宜(应该是开头或结尾的O(1))
通过编写/查找只是现有列表包装的List实现,可以对任意List实现进行O(1)操作。例如:
public class HeadedList implements List {
private final List tail;
public HeadedList(T head, List tail) {
this.head = head;
this.tail = tail;
}
public T get(int index) {
return index == 0 ? head : tail.get(index - 1);
}
...
}
(注意如果你使用像Lisp / Clojure / etc这样的语言,你会习惯于以这种方式思考列表)
但是,如果基准测试显示真正的性能问题是由列表构建引起的,那么只会烦恼。
答案 1 :(得分:2)
如果返回的List impl是ArrayList,则两个选项都相同:O(n)。
如果返回的impl是LinkedList,则在头部插入是O(1)。
总有一个O(1)选项:创建一个由返回列表支持的List包装类,但允许在内部插入插入元素。您必须创建一个自定义迭代器来迭代插入的elemdnt然后委托给列表。大多数方法都需要类似的定制。
如果它只有1000个左右的元素我不会打扰,除非您的应用程序已经完成并且您已经确定在此操作中存在可测量且严重的性能问题。
如果你在头部插入多个元素,那么你会点击一次创建一个LinkedList,然后每次插入都是O(1),但是因为你只有1要插入,不要打扰。
KISS:只需将元素插入返回的列表即可。我确信它会更快,而且很可能比图书馆更快。