书接上一篇ArrayList源码解析,这一节继续分析LinkedList在Java8中的实现,它同样实现了List接口,不过由名字就可以知道,内部实现是基于链表的,而且是双向链表,所以Linked List在执行像插入或者删除这样的操作,效率是极高的,相对地,在随机访问方面就弱了很多。
本文基于JDK1.8中LinkedList源码分析
####类定义
1 | 复制代码public class LinkedList<E> |
由上图以及类定义片段可知,LinkedList继承了AbstractSequentialList并且实现List,Deque,Cloneable, Serializable接口。
其中,AbstractSequentialList相较于AbstractList(ArrayList的父类)
,只支持次序访问,而不支持随机访问,因为它的get(int index)
,set(int index, E element)
, add(int index, E element)
, remove(int index)
都是基于迭代器实现的。所以在LinkedList使用迭代器遍历更快,而ArrayList使用get (i)更快。
接口方面,LinkedList多继承了一个Deque接口,所以实现了双端队列的一系列方法。
####基本数据结构
1 | 复制代码transient int size = 0; |
LinkedList中主要定义了头节点指针,尾节点指针,以及size用于计数链表中节点个数。那么每一个Node的结构如何呢?
1 | 复制代码private static class Node<E> { |
可以看出,这是一个典型的双向链表的节点。
LinkedList先不从初始化聊起,首先谈插入与删除。
####获取节点
获取节点是相对比较简单的操作, LinkedList提供了:
- getFirst获取头节点
- getLast获取尾节点
- get(int index) 获取指定位置的节点
1 | 复制代码public E getFirst() { |
检查非空后,直接返回first节点的item
1 | 复制代码public E getLast() { |
检查非空后,直接返回last节点的item
1 | 复制代码public E get(int index) { |
首先检查index范围,然后调用node(index)获取index处的节点,返回该节点的item值。
看看node(index)的实现,后面很多地方借助于这个小函数:
1 | 复制代码Node<E> node(int index) { |
由上面可以看出,在链表中找一个位置,只能通过不断遍历。
另外还有IndexOf,LastIndexOf操作,找出指定元素在LinkedList中的位置:
也是一个从前找,一个从后找,只分析下IndexOf操作:
1 | 复制代码public int indexOf(Object o) { |
主要也是不断遍历,找到值相等的节点,返回它的Index。
####更改节点的值
主要也就是一个set函数
1 | 复制代码 public E set(int index, E element) { |
根据index找到指定节点,更改它的值,并且会返回原有值。
####插入节点
LinkedList实现了Deque接口,支持在链表头部和尾部插入元素:
1 | 复制代码public void addFirst(E e) { |
这里我们可以看到内部实现的函数是linkFirst
,linkLast
,
链表头插入元素:
1 | 复制代码private void linkFirst(E e) { |
主要流程:
- 将原有头节点保存到f
- 将插入元素包装成新节点,并且该新节点的next指向原来的头节点,即f
- 如果原来的头节点f为空的话,那么新插的头节点也是last节点,否则,还要设置f的前置节点为NewNode,即NewNode现在是first节点了
- 记得增加size,记录修改次数modCount
链表尾插入元素
1 | 复制代码void linkLast(E e) { |
在指定index处插入元素
1 | 复制代码public void add(int index, E element) { |
首先调用checkPositionIndex检查index值是否在范围内,如果index在最后的话,就调用在尾部插入的函数,否则调用LinkBefore,主要看LinkBefore如何实现的:
1 | 复制代码void linkBefore(E e, Node<E> succ) { //在succ节点前插入newNode |
其余几个常用的add方法也是基于以上函数:
1 | 复制代码public boolean add(E e) { |
add
函数默认在函数尾部插入元素
1 | 复制代码public boolean addAll(Collection<? extends E> c) { |
addAll(Collection<? extends E> c)
指的是在list尾部插入一个集合,具体实现又依赖于addAll(size,c)
,指的是在指定位置插入一个集合:
1 | 复制代码public boolean addAll(int index, Collection<? extends E> c) { |
具体流程可以看代码中的注释。
####删除节点####
因为实现了Deque
的接口,所以还是实现了removeFirst
, removeLast
方法。
1 | 复制代码public E removeFirst() { |
首先保存fisrt节点到f,如果为空,抛出NoSuchElementException
异常,实际还是调用unlinkFirst
完成操作。
1 | 复制代码private E unlinkFirst(Node<E> f) { |
removeLast的实现基本和removeFirst对称:
1 | 复制代码public E removeLast() { |
主要实现还是借助于unlinkLast
:
1 | 复制代码private E unlinkLast(Node<E> l) { |
在指定index删除
1 | 复制代码public E remove(int index) { |
首先也是检查index是否合法,否则抛出IndexOutOfBoundsException
异常。
如果删除成功,返回删除的节点。
具体实现依赖于unlink
,也就是unlink
做实际的删除操作:
1 | 复制代码E unlink(Node<E> x) { |
根据图和代码来看,删除一个节点有以下几步:
- 将删除的节点保存在element里,同时把要删除节点的前驱节点标记为prev,后继节点标记为next;
- 如果prev为空,那么next节点直接为first节点,反之把prev的next指向next节点,如图中上面弯曲的红色箭头所示;
- 如果next为空,那么prev节点直接为last节点,反之把next的prev指向prev节点,如图中下面弯曲的蓝色箭头所示;
- 把要删除的节点置空,返回第一步保存的element。
还有种删除是以删除的元素作为参数:
1 | 复制代码public boolean remove(Object o) { |
- o为null,也是遍历链表,找到第一个值为null的节点,删除;
- o部位空,遍历链表,找到第一个值相等的节点,调用unlink(x)删除。
####清空列表
1 | 复制代码public void clear() { |
由代码看出,也就是循环遍历整个链表,将每个节点的每个属性都置为空。
LinkedList还定义了很多别的方法,基本上和上面分析的几个函数功能类似
- elemet和GetFirst一样,都返回列表的头,并且不移除它,如果列表为空,都会抛出NoSucnElement异常;
- peek也会返回第一个元素,但是为空时返回null, 不抛异常;
- remove方法内部就是调用removeFirst,所以表现相同,返回移除的元素,如果列表为空,都会抛出NoSucnElement异常;
- poll也是移除第一个元素,只是会在列表为空时只返回null;
- offer和offerLast在尾部add节点, 最终调用的都是addLast方法,offerFirst在头保护add节点,调用的就是addFirst方法;
- peekFirst返回头节点,为空时返回null,peekLast返回尾节点,为空时返回null,都不会删除节点;
- pollFirst删除并返回头节点,为空时返回null ,pollLast删除并返回尾节点,为空时返回null;
- push和pop也是让LinkedList具有栈的功能,也只是调用了addFirst和removeFirst函数。
####ListIterator
最后重点说一下LinkedList中如何实现了ListIterator迭代器。
ListIterator是一个更加强大的Iterator迭代器的子类型,它只能用于各种List类的访问。尽管Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,可以用set()
方法替换它访问过得最后一个元素。
定义如下:
1 | 复制代码public ListIterator<E> listIterator(int index) { |
指定index,可以在一开始就获取一个指向index位置元素的迭代器。
实际上LinkedList是实现了ListItr类:
1 | 复制代码private class ListItr implements ListIterator<E> { |
我们可以发现在ListIterator的操作中仍然有checkForComodification
函数,而且在上面叙述的各种操作中还是会记录modCount,所以LinkedList也是会产生快速失败事件的。
本文转载自: 掘金