繁体   English   中英

Java 8 数组列表。 哪个更快? 在索引 0 处插入一项或使用一项创建新列表并将所有添加到新列表?

[英]Java 8 ArrayList. Which is it faster? Insert one item at index 0 or create new List with one item and addAll to the new List?

让我们假设我调用第三方 API 并返回一个可变的 N 多对象列表。 该列表可以小到 10 个对象,也可以大到几千个。 然后我总是想在返回的列表的索引 0 处插入一个对象。 我知道我可以轻松地在索引 0 处调用 add ,但这将是 O(n),因为它会为插入移动每个对象。 我的问题是,使用我计划在开始时插入的项目创建一个新列表,然后在返回的第 3 方 N 多列表中调用该新列表上的 addAll 平均会更快(处理明智)吗?

这取决于列表实现。 如果您真的不了解第三方为您提供的列表实现,您所能做的就是经验测试和基准测试。

更有可能的是,他们正在返回你的标准Java列表类型中的一种,而事实上你已经标记了问题arraylist -是,你给什么?

ArrayList.add(index,element)使用System.arrayCopy()将每个移动的元素从索引n复制到n+1 ,然后将新元素写入其插槽。 这是 O(n),但它确实可能非常快,因为它将使用高度优化的系统memmove例程一次移动整个 RAM 块。 (请参阅为什么 memcpy() 和 memmove() 比指针增量快? )。

此外,如果您的额外元素使列表的大小超过分配的后备数组的大小,Java 将创建一个新数组并将整个数组arraycopy到那里。

请记住,您只是在复制对象引用,而不是整个对象,因此对于 1000 个元素,您正在复制(在 64 位机器上的最坏情况)64 位 * 1000 == 8 KB 的 RAM。

尽管如此,对于非常庞大的列表,它所花费的时间可能会变得很重要。 插入链表更便宜(在开始或结束时应该是 O(1))

您可以通过编写/查找一个 List 实现来使其成为对任意 List 实现的 O(1) 操作,该实现只是现有列表的一个包装器。 例如:

public class HeadedList<T> extends AbstractList<T> {

    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);
    }

    public int size() {
        return tail.size() + 1;
    }
}

(注意,如果您使用 Lisp/Clojure/etc 等语言工作,您会非常习惯以这种方式思考列表)

但是,只有在基准测试显示真正的性能问题是由列表构建引起的时候才需要考虑这个问题。

如果返回的 List impl 是 ArrayList,则两个选项相同:O(n)。
如果返回的 impl 是 LinkedList,则在头部插入是 O(1)。

总是有一个 O(1) 选项:创建一个 List 包装类,它由返回的列表支持,但允许通过在内部存储插入的元素在头部插入。 您必须创建一个自定义迭代器来迭代插入的元素,然后委托给列表。 大多数方法都需要类似的定制。

如果它只有 1000 个左右的元素,我不会打扰,除非您的应用程序是完整的,并且您已经确定在此操作中存在可衡量且足够严重的性能问题。

如果您在头部插入多个元素,那么您需要点击一次以创建一个 LinkedList,然后每次插入都是 O(1),但由于您只有 1 个要插入,所以不要打扰。

KISS:只需将元素插入返回的列表中。 我相信它会足够快,而且很可能比图书馆快得多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM