【问题标题】:How remove(Object obj) method in List in java is implemented?java中List中的remove(Object obj)方法是如何实现的?
【发布时间】:2014-05-23 05:59:50
【问题描述】:

谁能给出remove(Object obj)方法的实现是java中的List?

这个重载方法是如何以及在哪里定义的,ArrayList 中的实现是什么?

问题的第二部分是在 ArrayListLinkedList 之间,remove(int index) 方法在ArrayList 因为底层实现(一个是数组,另一个是链表)。但是 remove(object obj) 方法呢?哪个会更快?因为根据我的理解,两者都需要相同的时间。在ArrayList 中,迭代将在连续的内存位置上,而在LinkedList 中则不会。但是我们基本上必须遍历每个元素并使用 equals 检查需要获取哪个对象。

编辑:

我只是想我会提供一些背景,为什么我会问这个问题。我们经常被要求频繁插入和删除哪个 List 最好使用。我对此感到非常困惑。如果我们使用remove(int index)方法ArrayList可以跳转到位置(移动底层指针并删除对象)然后移动元素而对于LinkedList它需要从headNode遍历到索引并更改nextNode指向后面的值。哪个会更快,为什么?当我们开始使用remove(Object o) 方法时会发生什么?一些信息: When to use LinkedList over ArrayList?

【问题讨论】:

  • 没有这种方法。为什么你认为有?
  • Map 有一个get(Object key) 方法,你是这么想的吗?
  • 对不起!!我的意思是列表接口的remove方法实现的区别
  • List是抽象类,List的不同实现有自己的实现方法
  • @Salauyou List 是一个接口。问题的第二部分是关于 ArrayList 和 LinkedList 的实现

标签: java list arraylist linked-list


【解决方案1】:

您可能认为ArrayList 上的remove(int index)LinkedList 上的remove(int index) 更快,但事实并非如此。

为什么?您忽略了ArrayList 的最大成本之一:删除元素后调整数组大小的实际成本是多少。

这里提供了ArrayList 的实际实现:

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

现在,System.arraycopy 调用实际上是将现有数组的一半复制到新内存中,将它们连续排列,然后自行清理。实际上,在整个操作之后,每个元素都会向左移动 1。

无论在何处执行此操作(列表前面、列表中间、列表结尾),您都将 n-1 元素复制到连续内存中。这是一个 O(n) 操作。

现在,与LinkedList 的实现进行比较/对比(checkElementIndex 被省略,因为它很直观):

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

实现将从后面搜索或从前面搜索,具体取决于哪个更近(因此按位除以二,或size &gt;&gt; 1)。实际查找该位置涉及到一些工作,但一旦找到,就会执行一组非常具体且与大小无关的步骤,以确保保留列表的完整性。 p>

所以,无论我在哪里删除这个元素(前、中、尾),我都在做同样的工作来删除节点和调整我的列表。因此,运行时操作可以考虑为常数时间。

附录:

为了进一步阅读,在 ArrayList 中添加元素的行为与删除元素时的行为类似 - 您必须考虑到您的底层数组可能需要调整大小 在你可以输入之前,如果你输入 10,000 个元素并进行 1,000 次调整大小,你插入的数据是 O(n) 的顺序。

LinkedList 中插入对比,它只在前面或末尾插入;两者都可以直接访问,不涉及原始数组的任何内容,并且可以在恒定时间内完成。

【讨论】:

  • 我没有得到这部分:不管这个操作是在哪里执行的(列表的前面,列表的中间,列表的结尾),你复制 n-1 个元素到连续的记忆。这是一个 O(n) 操作。 对于 ArrayList,因为如果索引是最后一个(大小 -1),那么 numMoved 将为 0。因此,不应调整大小
  • 为什么你认为LinkedList 只插入到前面或末尾? LinkedList 实现与ArrayList 相同的List 接口,支持所有操作。除此之外,将单个元素插入LinkedList 所需的恒定时间乘以导致O(n) 时间复杂度的插入次数。 ArrayList 不会在每次插入时调整大小。由于它按一个因子调整大小,因此调整大小操作的总数与log n 成比例。所以当你添加10,000元素时,你有19调整大小,需要~28000参考复制操作,小于LinkedList...
  • 由于LinkedList每次插入需要更新四个节点引用,插入10,000元素需要更新40,000引用。因此,由于将 n 元素插入 ArrayList 具有 O(n) 时间复杂度,因此插入单个元素被称为具有摊销常数时间。所以尤其是对于大量元素,LinkedList 在这里没有优势。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-05
  • 2017-06-16
  • 1970-01-01
  • 2021-12-15
  • 2019-01-05
  • 2022-07-11
  • 1970-01-01
相关资源
最近更新 更多