LinkedList实现原理(JDK1.8)
LinkedList底层采用双向链表,如果对链表这种结构比较熟悉的话,那LinkedList的实现原理看明白就相当容易。
链表通过“指针”将一组零散的内存块串联起来使用,每一个元素(节点)通过指针指向它的下一个元素,最后一个节点的下一个指向为null,而双向链表就是除头节点的每一个元素都有指针同时再指向它的上一个元素。链表不同于数组,由于其地址的不连续,且元素占用大小的不确定,所以没法根据地址直接取值,获取元素时需要遍历访问,而双向链表相比于单向链表,一定程度上占用了额外的内存,但支持了双向遍历,加快了元素索取。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList继承于AbstractSequentialList抽象类,AbstractSequentialList又继承于AbstractList,最终实现了List的接口,所以LinkedList支持List集合的一些常规操作,但同时LinkedList又实现了Queue接口,所以LinkedList也支持队列的一些使用,例如peek、pop等。
1.成员变量
transient int size = 0; /** * Pointer to first node. * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * Pointer to last node. * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last;
LinkedList的主要成员变量就3个,大小size、头结点first、尾节点last,这也符合双向链表的特点,根据头或者尾部节点就可以遍历整个链表。至于节点类型,则是内部类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; } }
Node类就简简单单三个属性,也即双向链表节点必须的3个要素,当前节点数据item,下一节点next和上一节点prev。
2.构造方法
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); // 添加所有元素 addAll(c); }
LinkedList主要就两个构造方法,一个无参的构造,什么也没做,另一个接受集合的构造,该方法初始化了LinkedList对象并添加了所有集合c的元素。
至于addAll方法,我们后面再看。
LinkedList因为基于链表,所以不需要提前申请内存大小,也就不存在初始化指定容量大小,因而LinkedList是没有初始化指定size的构造方法的。
3.添加元素
3.1.尾部添加
LinkedList的添加方法有很多,首先最简单也是最常用的尾部添加。
public boolean add(E e) { linkLast(e); return true; }
主要逻辑就是linkLast(E e) 了。
void linkLast(E e) { final Node<E> l = last; // 构造新的节点,上一节点指向原来的last final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; // 操作数自增 modCount++; }
尾部添加,就是构造新的节点newNode,并将改节点的上一节点prev指向原来的last,同时改节点为新的last节点。l == null判断是否当前是第一次添加,如果 l 为 null,则newNode同时也是头结点,当前集合中仅有newNode一个元素,不为null时,因为双向链表,所有 l 的下一个节点next指向了newNode。
最后就是大小size自增与操作计数modCount的自增,尾部添加元素就完成了。
尾部操作除了add,还有个addLast(E e) 方法,两者除了返回值不一样,没有任何差别了,都是调用的linkLast方法。
public void addLast(E e) { linkLast(e); }
3.2.中间添加