ArrayList和LinkList实现的比较

一、ArrayList和LinkList实现的比较

1.使用get()获取元素
1.1 ArrayList.get()

​ 如果希望使用ArrayListget(int index)方法获取元素,实现可以简单地将这项任务委托给其内部数组:

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

    return elementData(index);
}

​ 当然,还会对给定索引执行额外检查(确保它不小于零或大于数组大小)。

我们可以看到这个操作是在常数时间内执行的,即O(1)。这意味着无论数组大小如何,任何请求的元素都将立即返回,而不需要遍历列表。这是因为整个数组存储在内存中的一个唯一位置。

第二个元素的槽正好位于第一个元素之后,第n个元素的槽正好位于第 n+1 个元素之前。依靠这个内部结构,任何元素都可以很容易地通过索引来获取。

1.2.LinkedList.get()

​ 如果希望从 a 中获取元素LinkedList,可以使用该get(int index)方法,但效率很低。

​ 前面我们提到过链表并不存在于内存中的单个位置,而是包含相互连接的不同节点。要获取元素,需要从头(或尾,以较接近者为准)遍历列表,并跟踪每个节点的连接,直到找到所需的元素。

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

Node<E> node(int 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;
    }
}

首先,进行检查以确保索引不大于0或大于LinkedList. 然后,该node()方法遍历列表,直到遇到我们要搜索的列表。

ArrayListO(1)时间相比,这是在O(N)时间内完成的。`

2.使用add()插入元素

​ 本质上,任何类型的插入都可以使用一种通用方法来概括和实现 - 在给定索引处插入。

​ 如果需要在开头插入元素,可以使用索引0调用该方法。如果需要在末尾插入元素,则索引将对应于列表的当前大小。如果需要在中间某处插入元素,则用户必须提供该索引。

2.1ArrayList.add()

​ 在末尾插入元素相当简单,特别是对于像ArrayList. 您只需将长度延长一,然后将元素插入到末尾:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

​ 然而,在给定位置插入有点棘手。您必须在要插入的位置破坏数组 - 复制该点之后的所有内容并将其移动到右侧,在索引处添加新元素:

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

​ 复制的部分越大,此操作就越慢。这使得添加元素的ArrayList操作效率相对较低。然而,到达应该完成插入的位置确实非常有效。

2.2LinkedList.add()

LinkedList的实现允许我们相当容易地在任何给定索引处添加元素。您只需将前一个元素和后一个元素的head指针tail分别指向新元素即可。如果您在列表的开头或末尾插入,则只需要更新一个指针。

在链表中间插入节点

public boolean add(E e) {
    linkLast(e);
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

无论列表有多大,只需要更改两个指针。这使得添加元素成为一种LinkedList高效的操作。然而,到达应该插入元素的位置是低效的。

3.使用indexOf0查找元素

​ 查找列表中的元素(无论是ArrayList还是 LinkedList) 应该非常相似。这是因为除非数组已排序且均匀分布,否则无法先验地知道任何特定元素的存储位置。

​ 列表只是记录其元素并提供操作它们的方法。为了准确地知道每个元素的位置,两种实现都需要经历某种迭代过程,直到找到该元素。

3.1ArrayList.indexOf()

在ArrayList实现中,这是通过一个简单的for循环从到0size-1并检查当前索引处的元素是否与给定值匹配来完成的:

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

这实际上是一种线性搜索,效率不是很高,但实际上是在打乱的集合中搜索元素的唯一方法(如果我们忽略元启发式算法和近似值)。

3.2LinkedList.indexOf()

LinkedList有些不同。它不必遍历数组,而是必须使用指针从一个元素跳转到下一个元素来遍历列表。最终,结果是相同的 - 逐一访问每个元素,直到找到要搜索的元素:

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

复制的部分越大,此操作就越慢。同样,这使得ArrayList从操作中删除元素变得低效。不过,ArrayList 的一个好处是您可以非常轻松地获得该元素。elementData(index)返回您希望在O(1)时间内删除的元素。

4.使用rremove()删除元素
4.1ArrayList.remove()

​ 与在给定索引处添加元素非常相似,删除它们需要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;
}

复制的部分越大,此操作就越慢。同样,这使得ArrayList从操作中删除元素变得低效。不过,ArrayLists的一个好处是您可以非常轻松地获得该元素。elementData(index)返回您希望在O(1)时间`内删除的元素。

4.2LinkedList.remove()

LinkedList通过取消我们要删除的元素的前一个和后一个指针的链接,可以从 a 中删除一个元素。之后,前一个元素链接到行中的下一个元素。这样,旧元素就“搁浅”了,没有对其的引用,GC 会处理它:

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

这使得LinkedList删除元素的操作变得``高效,因为同样只需要更改几个点。不过,列表越长,到达需要删除的元素所需的时间就越长,因为我们无法通过索引访问元素。

参考连接: https://stackabuse.com/difference-between-arraylist-and-linkedlist-in-java-code-and-performance/#performancecomparison

本文作者:用面条吃个鼻子

本文链接:https://www.cnblogs.com/reubenche/p/18344984

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   用面条吃个鼻子  阅读(91)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起