JDK源码之java.util.LinkedList
Posted 万万的猿语心声
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK源码之java.util.LinkedList相关的知识,希望对你有一定的参考价值。
JDK源码之java.util.LinkedList 类
一、LinkedList的定义
LinkedList是一个基于双向链表实现的List,每个节点都指向前一个和后一个节点从而形成链表。元素有序且可以重复,它除了作为List使用,还可以作为队列或者栈来使用。下面是LinkedList的继承体系。
1public class LinkedList<E>
2 extends AbstractSequentialList<E>
3 implements List<E>, Deque<E>, Cloneable, java.io.Serializable
4
二、ListedList的类图
1、如下3个接口和ArrayList一样
实现了java.util.List 接口,提供了基础的添加、删除、遍历等操作。
实现了java.io.Serializable 接口,可以被序列化。
实现了java.lang.Cloneable 接口,可以被克隆。
2、相对于ArrayList来说,LinkedList缺少了实现java.util.RandomAccess 接口,也就是说LiskedList不支持随机访问。
3、相对于ArrayList来说,LinkedList多了个实现了java.util.Deque 接口,提供双端队列的功能,LinkedList 支持快速的在头尾添加元素和读取元素,所以很容易实现该特性。也就是说LinkedList可以作队列使用,也可以当做栈来使用。
三、LinkedList源码分析
1、LinkedList的属性
size:链表大小
first:链表首节点
last:链表尾节点
1 /**
2 * 链表大小
3 */
4 transient int size = 0;
5 /**
6 * 链表首节点
7 */
8 transient Node<E> first;
9 /**
10 * 链表尾节点
11 */
12 transient Node<E> last;
2、LinkedList内部类
通过 Node 节点指向前后节点,从而形成双向链表。
first 和 last 属性:链表的头尾指针。
在初始时候,first 和 last 指向 null ,因为此时暂时没有 Node 节点。
在添加完首个节点后,创建对应的 Node 节点 node1 ,前后指向 null 。此时,first 和 last 指向该 Node 节点。
继续添加一个节点后,创建对应的 Node 节点 node2 ,其 prev = node1 和 next = null ,而 node1 的 prev = null 和 next = node2 。此时,first 保持不变,指向 node1 ,last 发生改变,指向 node2 。
size 属性:链表的节点数量。通过它进行计数,避免每次需要 List 大小时,需要从头到尾的遍历。
1 private static class Node<E> {
2 //实际存储的元素
3 E item;
4 //指向上一个节点的引用(前一个节点)
5 Node<E> next;
6 //指向下一个节点的引用(后一个节点)
7 Node<E> prev;
8 //构造函数
9 Node(Node<E> prev, E element, Node<E> next) {
10 this.item = element;
11 this.next = next;
12 this.prev = prev;
13 }
14 }
3、LinkedList的构造方法
LinkedList的构造方法,如下面源码中的显示是有两种的,第一个是默认的空的构造函数,第二个是将已有元素的集合Collection 的实例添加到 LinkedList 中,调用的是 addAll() 方法。
1 public LinkedList() {
2 }
3 public LinkedList(Collection<? extends E> c) {
4 this();
5 addAll(c);
6 }
4、LinkedList添加元素的方法
①、addFirst(E e) 将指定的元素插入到链表表头。
1//将指定的元素添加到链表表头
2 public void addFirst(E e) {
3 linkFirst(e);
4 }
5 private void linkFirst(E e) {
6 final Node<E> f = first;//将头节点赋值给f
7 //将指定元素构造成一个新节点,此节点的指向下一个节点的引用为头节点。
8 final Node<E> newNode = new Node<>(null, e, f);
9 //将新节点设置为头节点,原来的头节点f则变成第二个节点
10 first = newNode;
11 //如果第二节点为空则表示原来的链表为空
12 if (f == null)
13 //那么也把新节点设置为尾结点(原来已经设置过头节点)
14 last = newNode;
15 else
16 //把原来的头节点的上一个节点指向新节点
17 f.prev = newNode;
18 //节点数加1
19 size++;
20 // 修改次数加1,说明这是一个支持fail-fast的集合
21 modCount++;
22 }
②、addLast(E e)和add(E e) 将指定元素添加到链表尾
1//将指定元素添加到链表尾部
2 public void addLast(E e) {
3 linkLast(e);
4 }
5 public boolean add(E e) {
6 linkLast(e);
7 return true;
8 }
9 void linkLast(E e) {
10 //将l设置为尾结点
11 final Node<E> l = last;
12 //将指定元素构造成一个新节点,节点上一个节点指向尾结点l
13 final Node<E> newNode = new Node<>(l, e, null);
14 //将新节点设置为尾结点
15 last = newNode;
16 //如果原来的尾结点为空,则说明原来的链表是不存在的
17 if (l == null)
18 //那么把新节点设置为头节点(原来已经设置为尾结点)
19 first = newNode;
20 else
21 //将原来尾结点的下一个节点的引用指向新节点
22 l.next = newNode;
23 //节点加1
24 size++;
25 // 修改次数加1,说明这是一个支持fail-fast的集合
26 modCount++;
27 }
③、add(int index, E element) 将指定的元素插入此列表中的指定位置
1//将指定的元素插入此列表中的指定位置
2 public void add(int index, E element) {
3 //判断是否越界,判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
4 checkPositionIndex(index);
5 //如果索引值等于链表大小
6 if (index == size)
7 //直接把节点插入到尾结点
8 linkLast(element);
9 else
10 linkBefore(element, node(index));
11 }
12 void linkLast(E e) {
13 //将l设置为尾结点
14 final Node<E> l = last;
15 //将指定元素构造成一个新节点,节点上一个节点指向尾结点l
16 final Node<E> newNode = new Node<>(l, e, null);
17 //将新节点设置为尾结点
18 last = newNode;
19 //如果原来的尾结点为空,则说明原来的链表是不存在的
20 if (l == null)
21 //那么把新节点设置为头节点(原来已经设置为尾结点)
22 first = newNode;
23 else
24 //将原来尾结点的下一个节点的引用指向新节点
25 l.next = newNode;
26 //节点加1
27 size++;
28 // 修改次数加1,说明这是一个支持fail-fast的集合
29 modCount++;
30 }
31 Node<E> node(int index) {
32 // 因为是双链表
33 // 所以根据index是在前半段还是后半段决定从前遍历还是从后遍历
34 // 这样index在后半段的时候可以少遍历一半的元素
35 if (index < (size >> 1)) {
36 //如果 index 小于 size 的一半,就正序遍历,获得第 index 个节点
37 Node<E> x = first;//设置x为头节点
38 //从开始节点到插入节点索引之间的所有节点向后移动一位
39 for (int i = 0; i < index; i++)
40 x = x.next;
41 return x;
42 } else {// 如果 index 大于 size 的一半,就倒序遍历,获得第 index 个节点
43 Node<E> x = last;//将x设置为最后一个节点
44 //从最后节点到插入节点的索引位置之间的所有节点向前移动一位
45 for (int i = size - 1; i > index; i--)
46 x = x.prev;
47 return x;
48 }
49 }
50 // 在节点succ之前添加元素
51 void linkBefore(E e, Node<E> succ) {
52 // 获得 succ 的前一个节点
53 final Node<E> pred = succ.prev;//将pred设为插入节点的上一个节点
54 //将新节点的上引用设为pred,下引用设为succ
55 final Node<E> newNode = new Node<>(pred, e, succ);
56 //succ上一个节点的引用设置为新节点
57 succ.prev = newNode;
58 //如果插入节点的上一个节点的引用为空
59 if (pred == null)
60 first = newNode;//新节点就是头节点
61 else
62 pred.next = newNode;//插入节点的下一个节点引用设为新节点
63 size++;
64 modCount++;
65 }
④、addAll(Collection c) 按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾
1//按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾
2 public boolean addAll(Collection<? extends E> c) {
3 return addAll(size, c);
4 }
5/将集合 c 中所有元素插入到指定索引的位置。
6public boolean addAll(int index, Collection<? extends E> c) {
7 //判断是否越界,判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
8 checkPositionIndex(index);
9 //将集合C转换成一个Object数组
10 Object[] a = c.toArray();
11 int numNew = a.length;
12 //如果添加元素为空则返回false,数组未改变
13 if (numNew == 0)
14 return false;
15 //获得第 index 位置的节点 succ ,和其前一个节点 pred
16 Node<E> pred, succ;
17 //如果插入的位置等于链表的长度,就是将原来的集合附加到链表的末尾
18 if (index == size) {
19 succ = null;
20 pred = last;
21 } else {
22 // 如果 index 小于链表大小,则 succ 是第 index 个节点,prev 是 succ 的前一个二节点。
23 succ = node(index);
24 pred = succ.prev;
25 }
26 //遍历 a 数组,添加到 pred 的后面
27 for (Object o : a) {
28 //将遍历元素构建成一个新节点
29 @SuppressWarnings("unchecked") E e = (E) o;
30 Node<E> newNode = new Node<>(pred, e, null);
31 //如果index的上一个节点为空,那么说明头节点为空,那么直接就把头节点指向新节点
32 if (pred == null)
33 first = newNode;
34 else
35 // pred 下一个指向新节点
36 pred.next = newNode;
37 //修改pred指向新节点
38 pred = newNode;
39 }
40 //修改 succ 和 pred 的指向
41 if (succ == null) {
42 // 如果 succ 为 null ,说明插入队尾,则直接修改 last 指向最后一个 pred
43 last = pred;
44 } else {// 如果 succ 非 null ,说明插入到 succ 的前面
45 pred.next = succ;// prev 下一个指向 succ
46 succ.prev = pred;// succes 前一个指向 pred
47 }
48 //增加链表大小
49 size += numNew;
50 //增加数组修改次数
51 modCount++;
52 //返回 true 数组有变更
53 return true;
54 }
看到上面向 LinkedList 集合中添加元素的各种方式,我们发现LinkedList 每次添加元素只是改变元素的上一个指针引用和下一个指针引用,而且没有扩容。,对比于 ArrayList ,需要扩容,而且在中间插入元素时,后面的所有元素都要移动一位,两者插入元素时的效率差异很大,下一篇博客会对这两者的效率,以及何种情况选择何种集合进行分析。
还有,每次进行添加操作,都有modCount++ 的操作。
5、LinkedList的删除元素
①、remove()和removeFirst() 从此列表中移除并返回第一个元素
1 //从此列表中移除并返回第一个元素
2 public E remove() {
3 return removeFirst();
4 }
5
6 public E removeFirst() {
7 //设f为头节点
8 final Node<E> f = first;
9 //如果remove的时候如果没有元素则抛出异常
10 if (f == null)
11 throw new NoSuchElementException();
12 return unlinkFirst(f);
13 }
14 //删除指定节点f
15 private E unlinkFirst(Node<E> f) {
16 // assert f == first && f != null;
17 //头节点f的元素值
18 final E element = f.item;
19 //next 为头结点的下一个节点
20 final Node<E> next = f.next;
21 f.item = null;
22 f.next = null; // 将节点的元素及引用都设置为空,有助于GC
23 first = next;//修改头节点为第二节点
24 if (next == null)//如果第二元素为空(当前链表只存在第一元素)
25 last = null;//那么尾节点也置为空
26 else
27 next.prev = null;//如果第二节点不为空,则把第二节点的上一个节点引用置为空
28 size--;
29 modCount++;
30 return element;
31 }
②、removeLast() 从该列表中删除并返回最后一个元素
1 //从该列表中删除并返回最后一个元素
2 public E removeLast() {
3 final Node<E> l = last;
4 if (l == null)//如果尾结点为空则表示当前集合为空,抛出异常
5 throw new NoSuchElementException();
6 return unlinkLast(l);
7 }
8
9 private E unlinkLast(Node<E> l) {
10 // assert l == last && l != null;
11 final E element = l.item;
12 final Node<E> prev = l.prev;
13 l.item = null;
14 l.prev = null; // 把节点的元素及引用都只为空,1有助于GC
15 last = prev;//尾结点为倒数第二个元素
16 if (prev == null)//如果倒数第二个元素为空
17 first = null;//那么将节点也置为空
18 else
19 prev.next = null;//如果倒数第二元素不为空,那么把倒数第二元素的下一个引用置为空
20 size--;
21 modCount++;
22 return element;
23 }
③、remove(int index) 删除该列表中指定位置的元素
1//删除该列表中指定位置的元素
2 public E remove(int index) {
3 //判断是否越界,判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
4 checkElementIndex(index);
5 return unlink(node(index));
6 }
7// 删除指定节点x
8 E unlink(Node<E> x) {
9 // x的元素值
10 final E element = x.item;
11 //x的下一个节点
12 final Node<E> next = x.next;
13 //x的上一个节点
14 final Node<E> prev = x.prev;
15
16 if (prev == null) {//如果删除节点的上一个节点引用为空
17 first = next;//将头节点置为第一元素的下一个节点
18 } else {//如果删除节点的上一个节点引用不为空
19 prev.next = next;//将删除节点的上一个节点的下一个节点引用指向删除节点的下一个节点(去掉删除节点)
20 x.prev = null;//删除节点的上一个节点引用置为null
21 }
22
23 if (next == null) {//如果删除节点的下一个节点引用为null(表示删除最后一个节点)
24 last = prev;//将尾节点置为删除节点的上一个节点
25 } else {//不是删除尾节点
26 next.prev = prev;//将删除节点的下一个节点的上一个节点的引用指向删除节点的上一个节点
27 x.next = null;//将删除节点的下一个节点引用置为null
28 }
29
30 x.item = null;
31 size--;
32 modCount++;
33 return element;
34 }
④、remove(Object o) 如果存在,则从该列表中删除指定元素的第一次出现
此方法本质上和 remove(int index) 没多大区别,通过循环判断元素进行删除,需要注意的是,是删除第一次出现的元素,不是所有的。
1 public boolean remove(Object o) {
2 if (o == null) {
3 for (Node<E> x = first; x != null; x = x.next) {
4 if (x.item == null) {
5 unlink(x);
6 return true;
7 }
8 }
9 } else {
10 for (Node<E> x = first; x != null; x = x.next) {
11 if (o.equals(x.item)) {
12 unlink(x);
13 return true;
14 }
15 }
16 }
17 return false;
18 }
6、LinkedList 栈
栈的特性是LIFO(Last In First Out),所以作为栈使用也很简单,添加删除元素都只操作队列首节点即可。
1 public void push(E e) {
2 addFirst(e);
3 }
4 public E pop() {
5 return removeFirst();
6 }
四、LinkedList总结
LinkedList是一个以双链表实现的List;
LinkedList还是一个双端队列,具有队列、双端队列、栈的特性;
LinkedList在队列首尾添加、删除元素非常高效,时间复杂度为O(1);
LinkedList在中间添加、删除元素比较低效,时间复杂度为O(n);
LinkedList不支持随机访问,所以访问非队列首尾的元素比较低效;
*LinkedList在功能上等于ArrayList + ArrayDeque;
以上是关于JDK源码之java.util.LinkedList的主要内容,如果未能解决你的问题,请参考以下文章