leisurexi's Blog.

LinkedList

字数统计: 4.4k阅读时长: 19 min
2019/05/28 Share

LinkedList是一个以双向链表实现的List,它除了作为List使用,还可以作为队列或者堆栈使用。

LinkedList介绍

LinkedList继承关系

LinkedList简介

  1. LinkedList是一个继承于AbstractSequentialList的双向链表。它也可以被当做堆栈、队列或双端队列进行使用。
  2. LinkedList实现List接口,能让它进行队列操作。
  3. LinkedList实现Deque接口,即能将LinkedList当做双端队列使用。
  4. LinkedList实现Cloneable,即覆盖了函数clone(),能被克隆。
  5. LinkedList实现了java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
  6. LinkedList中的操作不是线程安全的。

LinkedList源码分析

AbstractSequentialList介绍

我们在看LinkedList之前先简单介绍下其父类AbstractSequentialList

AbstractSequentialList继承自AbstractList,是List接口的简化版实现。

AbstractSequentialList只支持按次序访问(链表在内存中不是按顺序存放的,而是通过指针连在一起,为了访问某一元素,必须从链头开始顺着指针才能找到某一个元素。),不像AbstractList可以随机访问。

想要实现一个支持次序访问的List的话,只需要继承这个类,并实现的指定的方法listIterator(int index)size()。实现ListIteratorhasNext()next()hasPrevious()previous()previousIndex()就可以获得一个不可修改的列表,如果你想让它可修改还需实现remove()set(E e)add(E e)方法。

属性

LinkedList的主要属性如下代码所示:

1
2
3
4
5
6
//链表节点的个数
transient int size = 0;
//链表首节点
transient Node<E> first;
//链表尾节点
transient Node<E> last;

关于transient关键字的最用可以查看我上次写的ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//内部静态类
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对象,如下:

1
2
public LinkedList() {
}

此时创建的是一个空的链表。

带Collection对象的构造函数

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

首先会调用无参数的构造方法,然后调用addAll方法将集合内元素全部加入到链表中,addAll方法我们后面会详细的分析。

从上述的俩个构造方法可以看出LinkedList是一个无界链表,不存在容量不足的问题。

添加元素

LinkedList主要提供addFirstaddLastaddaddAll等方法来实现元素的添加。下面我们一一来看:

1
2
3
4
//在链表首部添加元素
public void addFirst(E e) {
linkFirst(e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void linkFirst(E e) {
//将内部保存的首节点点赋值给f
final Node<E> f = first;
//创建新节点,新节点的next节点是当前的首节点
final Node<E> newNode = new Node<>(null, e, f);
//把新节点作为新的首节点
first = newNode;
//判断是否是第一个添加的元素
//如果是将新节点赋值给last
//如果不是把原首节点的prev设置为新节点
if (f == null)
last = newNode;
else
f.prev = newNode;
//更新链表节点个数
size++;
//将集合修改次数加1
modCount++;
}
1
2
3
4
//在链表尾部添加元素
public void addLast(E e) {
linkLast(e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void linkLast(E e) {
//将内部保存的尾节点赋值给l
final Node<E> l = last;
//创建新节点,新节点的prev节点是当前的尾节点
final Node<E> newNode = new Node<>(l, e, null);
//把新节点作为新的尾节点
last = newNode;
//判断是否是第一个添加的元素
//如果是将新节点赋值给first
//如果不是把原首节点的next设置为新节点
if (l == null)
first = newNode;
else
l.next = newNode;
//更新链表节点个数
size++;
//将集合修改次数加1
modCount++;
}
1
2
3
4
5
//该方法和addLast方差不多,因为是无界的,所以添加元素总是会成功
public boolean add(E e) {
linkLast(e);
return true;
}
1
2
3
4
5
6
7
8
9
10
  //该方法和上面add方法的区别是,该方法可以指定位置插入元素
public void add(int index, E element) {
//判断是否越界
checkPositionIndex(index);
//如果index等于链表节点个数,就将元素添加到俩表尾部,否则调用linkBefore方法
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  //获取指定位置的节点
Node<E> node(int index) {
//如果index小于size的一半,就从首节点开始遍历,一直获取x的下一个节点
//如果index大于或等于size的一半,就从尾节点开始遍历,一直获取x的上一个节点
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//将元素插入到指定节点前
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//拿到succ的上一节点
final Node<E> pred = succ.prev;
//创建新节点
final Node<E> newNode = new Node<>(pred, e, succ);
//将新节点作为succ的上一节点
succ.prev = newNode;
//判断succ是否是首节点
//如果是将新节点作为新的首节点
//如果不是将新节点作为pred的下一节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
//更新链表节点个数
size++;
//将集合修改次数加1
modCount++;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  //将集合内的元素依次插入index位置后
public boolean addAll(int index, Collection<? extends E> c) {
//判断是否越界
checkPositionIndex(index);
//将集合转换为数组
Object[] a = c.toArray();
int numNew = a.length;
//判断数组长度是否为0,为0直接返回false
if (numNew == 0)
return false;
//pred上一个节点,succ当前节点
Node<E> pred, succ;
//判断index位置是否等于链表元素个数
//如果等于succ赋值为null,pred赋值为当前链表尾节点last
//如果不等于succ赋值为index位置的节点,pred赋值为succ的上一个节点
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//循环数组
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//创建新节点
Node<E> newNode = new Node<>(pred, e, null);
//如果上一个节点为null,把新节点作为新的首节点,否则pred的下一个节点为新节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
//把新节点赋值给上一个节点
pred = newNode;
}
//如果index位置的节点为null,把pred作业尾节点
//如果不为null,pred的下一节点为index位置的节点,succ的上一节点为pred
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
//更新链表节点个数
size += numNew;
//将集合修改次数加1
modCount++;
//因为是无界的,所以添加元素总是会成功
return true;
}

看到上面这么多种方式添加元素,其实本质只是三种方式,在链表的首部、尾部、中间位置添加元素。如下图所示:

在链表首尾添加元素很高效,在中间添加元素比较低效,首先要找到插入位置的节点,在修改前后节点的指针。

删除元素

LinkedList提供了removeremoveFirstremoveLast等方法删除元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean remove(Object o) {
//因为LinkedList允许存在null,所以需要进行null判断
if (o == null) {
//从首节点开始遍历
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//调用unlink方法删除指定节点
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  //删除指定节点
E unlink(Node<E> x) {
//获取x节点的元素,以及它上一个节点,和下一个节点
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//如果x的上一个节点为null,说明是首节点,将x的下一个节点设置为新的首节点
//否则将x的上一节点设置为next,将x的上一节点设为null
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//如果x的下一节点为null,说明是尾节点,将x的上一节点设置新的尾节点
//否则将x的上一节点设置x的上一节点,将x的下一节点设为null
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
//将x节点的元素值设为null,等待垃圾收集器收集
x.item = null;
//链表节点个数减1
size--;
//将集合修改次数加1
modCount++;
//返回删除节点的元素值
return element;
}
1
2
3
4
5
6
   //删除指定位置的节点,其实和上面的方法差不多
//通过node方法获得指定位置的节点,再通过unlink方法删除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
1
2
3
4
//删除首节点
public E remove() {
return removeFirst();
}
1
2
3
4
5
6
7
8
//删除首节点
public E removeFirst() {
final Node<E> f = first;
//如果首节点为null,说明是空链表,抛出异常
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//删除首节点
private E unlinkFirst(Node<E> f) {
//首节点的元素值
final E element = f.item;
//首节点的下一节点
final Node<E> next = f.next;
//将首节点的元素值和下一节点设为null,等待垃圾收集器收集
f.item = null;
f.next = null; // help GC
//将next设置为新的首节点
first = next;
//如果next为null,说明说明链表中只有一个节点,把last也设为null
//否则把next的上一节点设为null
if (next == null)
last = null;
else
next.prev = null;
//链表节点个数减1
size--;
//将集合修改次数加1
modCount++;
//返回删除节点的元素值
return element;
}
1
2
3
4
5
6
7
8
//删除尾节点
public E removeLast() {
final Node<E> l = last;
//如果首节点为null,说明是空链表,抛出异常
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private E unlinkLast(Node<E> l) {
//尾节点的元素值
final E element = l.item;
//尾节点的上一节点
final Node<E> prev = l.prev;
//将尾节点的元素值和上一节点设为null,等待垃圾收集器收集
l.item = null;
l.prev = null; // help GC
//将prev设置新的尾节点
last = prev;
//如果prev为null,说明说明链表中只有一个节点,把first也设为null
//否则把prev的下一节点设为null
if (prev == null)
first = null;
else
prev.next = null;
//链表节点个数减1
size--;
//将集合修改次数加1
modCount++;
//返回删除节点的元素值
return element;
}

删除和添加一样,其实本质只有三种方式,即删除首部、尾部、中间节点。如下图所示:

和添加一样,首尾删除很高效,删除中间元素比较低效要先找到节点位置,再修改前后指针。

获取元素

LinkedList提供了getgetFirstgetLast等方法获取节点元素值。

1
2
3
4
5
6
7
//获取指定位置的元素值
public E get(int index) {
//判断是否越界
checkElementIndex(index);
//直接调用node方法获取指定位置的节点,并反回其元素值
return node(index).item;
}
1
2
3
4
5
6
7
8
//获取链表首节点的元素值
public E getFirst() {
final Node<E> f = first;
//判断是否是空链表,如果是抛出异常,否则直接返回首节点的元素值
if (f == null)
throw new NoSuchElementException();
return f.item;
}
1
2
3
4
5
6
7
8
//获取链表尾节点的元素值
public E getLast() {
final Node<E> l = last;
//判断是否是空链表,如果是抛出异常,否则直接返回尾节点的元素值
if (l == null)
throw new NoSuchElementException();
return l.item;
}

更新指定位置节点的元素值

1
2
3
4
5
6
7
8
9
10
11
public E set(int index, E element) {
//判断是否越界
checkElementIndex(index);
//指定位置的节点
Node<E> x = node(index);
E oldVal = x.item;
//设置新值
x.item = element;
//返回老值
return oldVal;
}

关于队列的操作

LinkedList可以作为FIFO(First In First Out)的队列,也就是先进先出的队列使用,以下是关于队列的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   //获取队列的第一个元素,如果为null会返回null
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取队列的第一个元素,如果为null会抛出异常
public E element() {
return getFirst();
}
//获取队列的第一个元素,如果为null会返回null
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//获取队列的第一个元素,如果为null会抛出异常
public E remove() {
return removeFirst();
}
//将元素添加到队列尾部
public boolean offer(E e) {
return add(e);
}

关于双端队列的操作

双端列队可以作为栈使用,栈的特性是LIFO(Last In First Out),也就是后进先出。所以作为栈使用也很简单,添加和删除元素都只操作队列的首节点即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
   //将元素添加到首部
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
//将元素添加到尾部
public boolean offerLast(E e) {
addLast(e);
return true;
}
//获取首部的元素值
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取尾部的元素值
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//删除首部,如果为null会返回null
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除尾部,如果为null会返回null
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//将元素添加到首部
public void push(E e) {
addFirst(e);
}
//删除首部,如果为null会抛出异常
public E pop() {
return removeFirst();
}
//删除链表中元素值等于o的第一个节点,其实和remove方法是一样的,因为内部还是调用的remove方法
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}

//删除链表中元素值等于o的最后一个节点
public boolean removeLastOccurrence(Object o) {
//因为LinkedList允许存在null,所以需要进行null判断
if (o == null) {
//和remove方法的区别它是从尾节点往前遍历
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
//调用unlink方法删除指定节点
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}

其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//判断元素是否存在于链表中
public boolean contains(Object o) {
return indexOf(o) != -1;
}

//从前往后查找返回节点元素值等于o的位置,不存在返回-1
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

//该方法和上面indexOf方法相反,它是从后往前查找返回节点元素值等于o的位置,不存在返回-1
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}

//克隆函数,返回LinkedList的克隆对象
public Object clone() {
LinkedList<E> clone = superClone();

// 将新建LinkedList置于最初状态
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;

// 将链表中所有节点的数据都添加到克隆对象中
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);

return clone;
}

//返回LinkedList节点单元素值的Object数组
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
//将链表中所有节点的元素值添加到object数组中
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}

// 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
public <T> T[] toArray(T[] a) {
//如果a的长度小于LinkedList节点个数,说明a不能容纳LinkedList的所有节点元素值
//则新建一个数组,数组大小为LinkedList节点个数,并赋值给a
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
// 将链表中所有节点的元素值都添加到数组a中
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;

if (a.length > size)
a[size] = null;

return a;
}

//将LinkedList中的数据写入到输入流中,先写容量,再写数据
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();

// Write out size
s.writeInt(size);

// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}

//从输入流中读取数据,一样是先读容量,再读数据
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read in size
int size = s.readInt();
//从输入流中将元素值逐个添加到链表中
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}

总结

  • LinkedList自己实现了序列化和反序列化,因为它实现了writeObject和readObject方法。
  • LinkedList是一个以双向链表实现的List。
  • LinkedList还是一个双端队列,具有队列、双端队列、栈的特性。
  • LinkedList在首部和尾部添加、删除元素效率高效,在中间添加、删除元素效率较低。
  • LinkedList虽然实现了随机访问,但是效率低效,不建议使用。
  • LinkedList是线程不安全的。
CATALOG
  1. 1. LinkedList介绍
    1. 1.1. LinkedList继承关系
    2. 1.2. LinkedList简介
  2. 2. LinkedList源码分析
    1. 2.1. AbstractSequentialList介绍
    2. 2.2. 属性
    3. 2.3. 构造函数
      1. 2.3.1. 无参构造函数
      2. 2.3.2. 带Collection对象的构造函数
    4. 2.4. 添加元素
    5. 2.5. 删除元素
    6. 2.6. 获取元素
    7. 2.7. 更新指定位置节点的元素值
    8. 2.8. 关于队列的操作
    9. 2.9. 关于双端队列的操作
    10. 2.10. 其他方法
  3. 3. 总结