java容器系列二(ArrayList和LinkedList源码梳理)

一,ArrayList

参考博文:
http://www.cnblogs.com/zhangyinhua/p/7687377.html## 标题
1,数组
在讲ArrayList之前我们首先讲一下数据结构中的数组。毕竟ArrayList是基于数组实现的List。
数组概述:数组中所有的元素都存储在操作系统分配的一个内存块中。通过使用特定元素的索引作为下标,可以在常数时间内访问元素。
数组优点:访问元素快
缺点:基于位置的插入操作实现复杂。如果要在数组中的给定位置插入元素,可能需要移动在数组中的其他元素。
2,ArrayList概述
1,ArrayList是动态增长和缩减的索引序列,基于数组实现的List。
2,它封装了一个动态再分配的Object[]数组,每个对象都有一个capacity属性,代表封装的Object[]数组数组长度。当向数组添加元素时,该属性值会自动增加
3,如果向ArrayList中添加大量元素,就可以使用ensureCapacity方法一次性增加capacity。
3,源码分析
java容器系列二(ArrayList和LinkedList源码梳理)_第1张图片

上图为ArrayList添加元素时方法的调用顺序,正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。核心就在grow方法,它是ArrayList自动扩展大小的核心之处。
下面我们挨个分析

/**
     * Appends the specified element to the end of this list.添加一个特定的元素到list的末尾。
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {    
    //确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。
        ensureCapacityInternal(size + 1);  // Increments modCount!!
     //在数据中正确的位置上放上元素e,并且size++
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) { //看,判断初始化的elementData是不是空的数组,也就是没有长度
    //因为如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是带这里,还没有真正的初始化这个elementData的大小。
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    //确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用
        ensureExplicitCapacity(minCapacity);
    }
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) { //看,判断初始化的elementData是不是空的数组,也就是没有长度
    //因为如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是带这里,还没有真正的初始化这个elementData的大小。
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    //确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用
        ensureExplicitCapacity(minCapacity);
    }

grow(xxx); arrayList核心的方法,能扩展数组大小的真正秘密。

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;  //将扩充前的elementData大小给oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
        if (newCapacity - minCapacity < 0)//这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作。
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
    //新的容量大小已经确定好了,就copy数组,改变容量大小咯。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

二,LinkedList

参考博文:
https://www.jianshu.com/p/56c77c517e71
1,双链表
单链表只能由开始结点走到中断结点。而双链表就在单链表的基础上增加了一个指针域,从而实现由后继找到前驱。从而实现输出终端节点到开始节点的数据序列。
java容器系列二(ArrayList和LinkedList源码梳理)_第2张图片
2,LinkedList概述
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181211152051138.png
LinkedList类声明如下:

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

可以发现 LinkedList继承了 AbstractSequentialList抽象类,而不是像 ArrayList和 Vector那样实现 AbstractList,实际上,java类库中只有 LinkedList继承了这个抽象类,正如其名,它提供了对序列的连续访问的抽象:
LinkedList的底层是 Deque双向链表,实现了 Deque接口,而 Deque接口继承于 Queue接口,因此,在java中,如果要实现队列,一般都使用 LinkedList来实现。
3,LinkedList源码分析
这里只分析几个核心的方法。
1,linkFirst()

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

逻辑还是比较简单,先构造一个新节点,他的前一个节点为空,元素为e,后一个节点为当前第一个节点
f。然后把新节点作为第一个节点。如果为空,则说明原来此链表为空,最后一个节点也赋值为新节点。
2,linkLast()
/**
* Links e as last element.
/
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++;
}
逻辑同linkFirst()
3,linkBefore(E e, Node succ)
/
*
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node succ) {
// assert succ != null;
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++;
}
构造新节点,前一个节点为当前节点的前一个节点,元素为e,后一个节点为当前节点。
判断方式和上面一样
4,add(int index, E element)
public void add(int index, E element) {
checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

逻辑比较明确,先判断是否超出位置,如果是最后一个位置的话,直接调用
linKLase在链表尾部插入。否则,找出当前索引的节点,然后在它前面插入。
其中,node(index)查找节点遍历时候,用了折半查找法。源码如下。

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

三,ArrayList和LinkedList比较

ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
LinkedList不支持高效的随机元素访问。
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,就存储密度来说,ArrayList是优于LinkedList的。  
  总之,当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能,当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

你可能感兴趣的:(java容器系列)