ArrayList和LinkedList源码阅读笔记

来源:互联网 发布:百度语音软件 编辑:程序博客网 时间:2024/04/27 21:20


List 接口继承自Collection接口,并在Collection接口的基础上添加了大量的方法,使得可以在List 的中间插入和删除元素。
List 按照插入的顺序来保存元素,它常用的实现类主要有两种:ArrayList和LinkedList,其中ArrayList 擅长随机访问元素操作,但是在中间插入和存取元素时比较慢;而LinkedList在中间进行插入和删除的操作比较快,随机访问方面相对较慢。只要阅读以下这两个类的底层实现方式,就很容易理解它们各自的优势。 

ArrayList:

ArrayList 的列表对象实质是存放在一个引用型的数组内,并通过不断的改变该引用型数组的指向来改变数组的容量。

ArrayList 类内部:

private transient Object[] element;//存放列表对象的数组的引用,private int size; //用来记录列表中实际对象的数量;同时还引入Capacity,用来不断调整存放对象的数组的大小。

ArrayList 随机访问元素:

因为底层是通过Object类型的数组实现,所以可以直接通过数组下标来访问任何元素。

 

ArrayList中间插入和删除元素:

   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的index位置中插入元素时,包括index以后的所有元素都要向后移动一位,需要进行很多的赋值操作,所以ArrayList在中间插入和删除代价较大。

 

当向ArrayList增加元素时,必须保证数组的容量能够确保这个元素还能再加进去。这时候如果数组的size与capacity的值一样,就必须增大capacity的值,使add操作能顺利进行。ArrayList 通过下面方法实现自动改变数组大小机制:

private void ensureCapacityInternal(intminCapacity) {        modCount++;        // add 方法前面通常会有ensureCapacityInternal(size + 1),当size + 1的值比数组容量的值小时,则不做任何操作,当size + 1的值比数组容量的值大时,则要扩大数组的容量。         if (minCapacity - elementData.length> 0)            grow(minCapacity);} private staticfinal int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;private voidgrow(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);       //这步操作是关键,通过改变elementData的引用来实现数组容量的自动改变机制。        element = Arrays.copyOf(elementData, newCapacity);   }   private static int hugeCapacity(intminCapacity) {        if (minCapacity < 0) //overflow            throw newOutOfMemoryError();        return (minCapacity > MAX_ARRAY_SIZE)?            Integer.MAX_VALUE :            MAX_ARRAY_SIZE;}

通过以上代码,我们可知java自动增加ArrayList大小的思路是:向ArrayList添加对象时,原对象数目加1后如果大于数组长度,则建一个适当长度的新数组,并将原数组的内容进行拷贝,然后让element对这个新数组进行引用。原数组会自动抛弃(java垃圾回收机制会自动回收)。

 

LinkedList:

LinkedList内部定义了Node类型的结点,其中包括列表对象的值,以及前一个结点和后一个结点的引用

    private static class Node<E> {        E item;        Node<E> next;        Node<E> prev;         Node(Node<E> prev, E element, Node<E>next) {            this.item = element;            this.next = next;            this.prev = prev;        }}


LinkedList 通过Node<E> first,Node<E> last两个属性用来标记该链表的首结点和尾结点。

 

LinkedList 在中间增加和删除元素:

LinkedList 找到index所在位置的结点,然后通过改变该位置结点的prev和next 的引用来进行插入和删除操作。

   

 public void add(intindex, E element) {        checkPositionIndex(index);         if (index == size)            linkLast(element);        else            linkBefore(element, node(index));    }    void linkBefore(E e, Node<E>succ) {        // assert succ != null;        final Node<E> pred = succ.prev;        final Node<E> newNode = newNode<>(pred, e, succ);        succ.prev = newNode;        if (pred == null)            first = newNode;        else            pred.next = newNode;        size++;        modCount++;}


LinkedList随机查找的操作:

  

  public E get(intindex) {        checkElementIndex(index);        return node(index).item;}   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;       }    }

如果index比size/2小,就从前往后找,如果index比size/2大,就从后往前找。虽然对查找进行了一定的优化,但是查找时间级别仍是O(n),而ArrayList随机访问的时间是O(1)。

0 0