ArrayList/LinkedList 的底层分析

ArrayList

ArrayList 实现于 List、RandomAccess 接口。能够插入空数据,也支持随机访问。java

ArrayList至关于动态数据,其中最重要的两个属性分别是: elementData 数组,以及 size 大小。 在调用 add() 方法的时候:node

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  • 首先进行扩容校验。
  • 将插入的值放到尾部,并将 size + 1 。

若是是调用 add(index,e) 在指定位置添加的话:数组

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++;
    }
  • 也是首先扩容校验。
  • 接着对数据进行复制,目的是把 index 位置空出来放本次插入的数据,并将后面的数据向后移动一个位置。

其实扩容最终调用的代码:dom

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

也是一个数组复制的过程。性能

因而可知 ArrayList 的主要消耗是数组扩容以及在指定位置添加数据,在平常使用时最好是指定大小,尽可能减小扩容。更要减小在指定位置插入数据的操做this

 

LinkedList 底层分析

如图所示 LinkedList 底层是基于双向链表实现的,也是实现了 List 接口,因此也拥有 List 的一些特色(JDK1.7/8 以后取消了循环,修改成双向链表)。spa

新增方法

public boolean add(E e) {
        linkLast(e);
        return true;
    }
     /**
     * Links e as last element.
     */
    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++;
    }

可见每次插入都是移动指针,和 ArrayList 的拷贝数组来讲效率要高上很多。指针

查询方法

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

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

上述代码,利用了双向链表的特性,若是index离链表头比较近,就从节点头部遍历。不然就从节点尾部开始遍历。使用空间(双向链表)来换取时间。code

  • node()会以O(n/2)的性能去获取一个结点
    • 若是索引值大于链表大小的一半,那么将从尾结点开始遍历

这样的效率是很是低的,特别是当 index 越接近 size 的中间值时。blog

总结:

  • LinkedList 插入,删除都是移动指针效率很高。
  • 查找须要进行遍历查询,效率较低。
  • 特例:再插入数据时,若是事先设定好数组的长度,避免数组扩容的话,其实效率比linkedlist的插入效率还要快,不信的小伙伴能够去尝试一下。
相关文章
相关标签/搜索