集合-ArrayList和LinkedList常见源码及异同

792 阅读7分钟

ArrayList

继承关系

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

内部结构和初始化

底层是可变数组

    /**
     * Default initial capacity.
     * 初始化容量,这个没发现在初始化的时候使用,在扩容的时候有使用
     */
    private static final int DEFAULT_CAPACITY = 10;
 transient Object[] elementData; 

transient关键字修饰 表示不会被序列化 transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
    *// Write out element count, and any hidden stuff*
        int expectedModCount = modCount;
    s.defaultWriteObject();
    *// Write out array length*
        s.writeInt(elementData.length);
    *// Write out all elements in the proper order.*
        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
}

每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,

然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

传参的构造方法,构造对应的长度的数组,如果小于0,则抛出错误。

 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * Constructs an empty list with an initial capacity of ten.
     * 构造一个初始容量为10的空列表。
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

这里无参构造器,初始化空的Object数组。

内部结构就是一个Object的数组

扩容

在添加元素时会涉及到扩容,

添加元素时会进行 1.增加数组长度,2.将元素添加到数组中

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!! 1 增加长度
        elementData[size++] = e; // 添加元素到数组
        return true;
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity); //DEFAULT_CAPACITY=10,如果是空数组,就返回10
        }
        return minCapacity;
        }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //modCount默认为0

        // overflow-conscious code
        if (minCapacity - elementData.length > 0) //如果需要的长度大于原来数组长度需要扩容
            grow(minCapacity);
    }

扩容方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容1.5倍
        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);//进行复制
    }

小结

1.ArrayList 可以存放null;

2.ArrayList线程不安全;

3.ArrayList内部实际上是一个Object的数组;

4.ArrayList每次扩容是1.5倍;

5.ArrayList本质是一个数组所以查询很快,新增和删除要慢些;

6.ArrayList底层数组存/取元素效率非常的高(get/set),查找,时间复杂度是O(1),插入和删除元素效率似乎不太高,时间复杂度为O(n)。

LinkedList

继承关系

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

内部结构和初始化

底层是双向链表,

双向链表:是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。

链表的尾部元素的后一个节点是链表的头节点;而链表的头结点前一个节点则是则是链表的尾节点

transient int size = 0; //集合的长度
transient Node<E> first;//双向链表头部节点
transient Node<E> last;//双向链表尾部节点

无参构造方法

public LinkedList() {
    }

传入一个list的构造方法

public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

addAll的源码解析

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);//越界检查

        Object[] a = c.toArray();//集合转数组
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;//尾插,得到插入位置的前继节点和后继节点
        if (index == size) {//初始化尾插,就直接index=size
            //从尾部添加的情况:前继节点是原来的last节点;后继节点是null
            succ = null;
            pred = last;
        } else {
            // 从指定位置(非尾部)添加的情况:前继节点就是index位置的节点,后继节点是index位置的节点的前一个节点
            succ = node(index);
            pred = succ.prev;
        }
        //循环插入
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null) 
                //空链插入情况
                first = newNode;
            else //不为空,将pred下一个指针指向当前的元素
                pred.next = newNode;
            pred = newNode;//最后将newNode赋值给pred,给下一次循环使用,目的是后移,(更新前置节点为最新插入的节点(的地址))
        }

        if (succ == null) {
            //如果是从尾部开始插入的,则把last置为最后一个插入的元素
            last = pred; //循环完后如果头元素为空,则将最后一个元素赋值为头,形成尾部跟头部的关联关系
        } else {
            // 如果不是从尾部插入的,则把尾部的数据和之前的节点连起来
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

双向链表是 内部类定义了一个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。

其中Node有三个属性: item:实际存放的元素

next:下一个节点的引用,指向下一个节点

prev:前一个节点的引用,指向上一个节点

这样就构成了一个链表结构。

添加方法

LinkedList即实现了List接口,又实现了Deque接口,

所以LinkedList既可以添加将元素添加到尾部,也可以将元素添加到指定索引位置,还可以添加添加整个集合;

另外既可以在头部添加,又可以在尾部添加。

//将元素添加到尾部
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节点也为需要插入的节点。(也就是说:该节点既是头节点first也是尾节点last)
            first = newNode;
        else
            // 不是空链表的情况:将原来的尾部节点(现在是倒数第二个节点)的next指向需要插入的节点
            l.next = newNode;
        size++;// 更新链表大小和修改次数,插入完毕
        modCount++;
    }

private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

在指定位置添加

public void add(int index, 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 = new Node<>(pred, e, succ);// 创建新节点,其前继节点是succ的前节点,后接点是succ节点
        succ.prev = newNode;// 更新插入位置(succ)的前置节点为新节点
        if (pred == null)
            first = newNode;// 如果pred为null,说明该节点插入在头节点之前,要重置first头节点 
        else
            pred.next = newNode;// 如果pred不为null,那么直接将pred的后继指针指向newNode即可
        size++;
        modCount++;
    }

扩容

由于它的底层是用双向链表实现的,没有初始化大小,也没有扩容的机制。

小结

1.LinkedList 可以存放null;

2.LinkedList线程不安全;

3.LinkedList内部实际上是循环的双向链表;

4.LinkedList没有扩容机制也没有初始化;

5.LinkedList本质是双向链表,所以查询慢(需要遍历),新增和删除要快些;

6.LinkedList查找,时间复杂度是O(n),插入(尾插为为O(1),指定位置插为O(n)),删除时间复杂度为O(n)。

ArrayList和LinkedList比较

1.LinkedList和ArrayList的差别主要来自于Array和LinkedList数据结构的不同。ArrayList是基于数组实现的,LinkedList是基于双链表实现的。

2.对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。

使用场景

使用场景:

1.如果应用程序对数据有较多的随机访问,ArrayList对象要优于LinkedList对象;

2.如果应用程序有更多的插入或者删除操作,较少的数据读取,LinkedList对象要优于ArrayList对象;

3.注意ArrayList的插入,删除操作也不一定比LinkedList慢,如果在List靠近末尾的地方插入,

那么ArrayList只需要移动较少的数据,而LinkedList则需要一直查找到列表尾部,

反而耗费较多时间,这时ArrayList就比LinkedList要快。

写在后面的话

你是守宫砂,你是心头明月光