从源码对比 ArrayList 和 LinkedList

本贴最后更新于 2436 天前,其中的信息可能已经时移世异

1. 存储结构

ArrayList:数组

transient Object[] elementData;

LinkedList:链表

transient Node first; transient Node last;

2.扩容

ArrayList:1.5 倍

private void grow(int minCapacity) { int oldCapacity = elementData.length; //新容量为旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 以新容量为长度创建一个新的数组,并把旧数组拷贝进去 elementData = Arrays.copyOf(elementData, newCapacity); }

LinkedList:不需要扩容

3.Get 方法

ArrayList: 直接根据下标从数组中获取元素

public E get(int index) { rangeCheck(index); return elementData(index); } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }

LinkedList: 遍历一半的链表

public E get(int index) { checkElementIndex(index); return node(index).item; } Node node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }

4.add 方法

a) 尾部添加新元素

ArrayList: 如果容量足够的话,直接在数组尾部添加元素,否则先扩容再添加元素

public boolean add(E e) { //判断是否需要扩容 ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }

LinkedList:若链表为空,则头尾为同一个新节点,否则在链表尾部添加新节点

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

b) 中间添加新元素

ArrayList: 将要插入的位置之后的元素向后拷贝,然后在该位置插入新元素

public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); //通过拷贝,将index位置之后的所有元素都后移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }

LinkedList: 在指定节点的前面创建一个新节点,并改变相应的指向即可。

public void add(int index, E element) { checkPositionIndex(index); if (index == size) //在最后插入元素 linkLast(element); else linkBefore(element, node(index)); } void linkBefore(E e, Node succ) { final Node pred = succ.prev; //创建一个新节点,前节点为指定节点的前节点,后节点为指定节点 final Node newNode = new Node<>(pred, e, succ); //使指定节点的前节点指向新节点 succ.prev = newNode; if (pred == null) //新节点为首节点 first = newNode; else //指定节点的前节点指向新节点 pred.next = newNode; size++; modCount++; }

5.删除元素

ArrayList:将要删除的下标之后的所有元素向前拷贝一位,并将最后一位置为 null

public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) //将index+1之后的元素都向前移动一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }

LinkedList: 先遍历找到节点,然后删除该节点即可

public E remove(int index) { checkElementIndex(index); //通过遍历找到要删除的节点 return unlink(node(index)); } E unlink(Node x) { // assert x != null; final E element = x.item; final Node next = x.next; final Node prev = x.prev; if (prev == null) { //要删除的节点是首节点,就讲first节点指向下一个节点 first = next; } else { //不是首节点,就讲前节点的后节点指向为目标节点的后节点 prev.next = next; x.prev = null; } if (next == null) { //要删除的节点是最后一个节点,就讲last节点指向目标节点的前节点 last = prev; } else { //不是最后一个节点,就讲后节点的前节点指向为目标节点的前节点 next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }

6.总结

ArrayList 使用数组实现的,所以在查找元素方面比较有优势,可是涉及扩容,向中间插入节点时需要拷贝部分数组,比较消耗资源。
LinkedList 是使用链表实现的,所以在插入元素比较有优势,但是也需要遍历链表去查找节点。不过在频繁插入时仍然比数组有优势。但是在查找元素时,需要遍历一半的链表,不如数组方便。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3197 引用 • 8215 回帖
  • 代码
    467 引用 • 586 回帖 • 9 关注
  • 集合
    14 引用 • 8 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...