前言
LinkedList是我们比较常用的数据类型之一,也是面试中比较容易问到的集合之一。本篇文章主要通过分析LinkedList的源码。
本文基于jdk1.8
LinkedList简介
LinkedList是基于双向循环链表实现的,它的头结点不存放数据。
LinkedList类结构
可以从上图看出,LinkedList继承AbstractSequentialList抽象类,实现了List,Deque,Cloneable,Serializable接口。
LinkedList特点
- LinkedList非线程安全。
- LinkedList允许插入null。
- LinkedList中的数据存储是有序的。
- LinkedList中的数据可以重复。
LinkedList提供的API
方法名 | 方法解释 |
---|---|
add(E e) | 将指定的元素添加到LinkedList的末尾。 |
add(int index, E element) | 将指定的元素插入到LinkedList中的指定位置。 |
addAll(Collection<? extends E> c) | 将指定的集合中的所有元素添加到LinkedList的末尾。 |
addAll(int index, Collection<? extends E> c) | 将指定的集合中的所有元素添加到LinkedList中的指定位置。 |
addFirst(E e) | |
addLast(E e) | |
clear() | 删除LinkedList中的所有元素。 |
clone() | LinkedList浅拷贝。 |
contains(Object o) | 判断LinkedList中是否包含指定元素。 |
descendingIterator() | |
element() | |
get(int index) | 获取LinkedList中指定位置的元素。 |
getFirst() | |
getLast() | |
indexOf(Object o) | 返回LinkedList中指定元素第一次出现的索引,如果此列表不包含元素,则返回-1。 |
lastIndexOf(Object o) | 返回LinkedList中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。 |
listIterator(int index) | 从LinkedList中指定的位置开始,返回LinkedList中元素的列表迭代器。 |
offer(E e) | |
offerFirst(E e) | |
offerLast(E e) | |
peek() | |
peekFirst() | |
peekLast() | |
poll() | |
pollFirst() | |
pollLast() | |
pop() | |
push(E e) | |
remove() | |
remove(int index) | 删除LinkedList中指定位置的元素。 |
remove(Object o) | 从LinkedList中删除第一次出现的指定元素(如果存在)。 |
removeFirst() | |
removeFirstOccurrence(Object o) | |
removeLast() | |
removeLastOccurrence(Object o) | |
set(int index, E element) | 用指定的元素替换LinkedList中指定位置的元素。 |
size() | 获取LinkedList大小。 |
spliterator() | |
toArray() | 将LinkedList转换为数组。 |
toArray(T[] a) | 将LinkedList转换为指定类型的数组。 |
LinkedList源码
LinkedList成员变量
1 | /** |
LinkedList内部类
Node
1 | private static class Node<E> { |
LinkedList构造函数
LinkedList一共有两个构造函数
1 | /** |
LinkedList重要方法解析
add(E e)
1 | public boolean add(E e) { |
linkLast(E e)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void linkLast(E e) {
// 获取之前的尾节点
final Node<E> l = last;
// 根据e创建新的节点,该节点的前一个节点为之前的尾节点,后一个节点为null
final Node<E> newNode = new Node<>(l, e, null);
// 将新的节点设置为尾节点
last = newNode;
// 如果之前的尾节点为null,则新增的节点也是头节点
if (l == null)
first = newNode;
// 如果之前的尾节点不为null,将之前的尾节点的下一个节点设置为新的节点
else
l.next = newNode;
// LinkedList大小加1
size++;
// 操作次数加1
modCount++;
}add(E e)
流程:
- 保存原来的尾节点
- 将需要插入的元素包装成新的节点,该节点的前一个节点为原来的尾节点,后一个节点为null
- 将新的节点设置为尾节点
- 如果原来的尾节点为null,则设置新的节点为头节点
- 如果原来的尾节点不为null,将原来节点的下一个节点设置为新的节点
- LinkedList大小加1,操作次数加1,并返回成功
add(int index, E element)
1 | public void add(int index, E element) { |
checkPositionIndex(int index)
1
2
3
4
5private void checkPositionIndex(int index) {
// 如果定位越界,抛出越界异常
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}isPositionIndex(int index)
1
2
3
4private boolean isPositionIndex(int index) {
// 返回定位是否越界
return index >= 0 && index <= size;
}node(int index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Node<E> node(int index) {
// assert isElementIndex(index);
// 判断定位是否小于LinkedList大小的一半,是的话,从头节点开始遍历,不是话从尾节点开始遍历
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;
}
}linkBefore(E e, Node
succ) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 记录定位到的节点的前一个节点
final Node<E> pred = succ.prev;
// 根据e创建新的节点,新节点的前一个节点为定位到的节点的前一个节点,后一个节点为定位到的节点
final Node<E> newNode = new Node<>(pred, e, succ);
// 设置定位到的节点前一个节点为新的节点
succ.prev = newNode;
// 如果定位到的节点的前一个节点为null,则新的节点为头节点
if (pred == null)
first = newNode;
// 如果定位到的节点的前一个节点不为null,将前一个节点的下一个节点设置为新的节点
else
pred.next = newNode;
// LinkedList大小加1
size++;
// 操作次数加1
modCount++;
}
addAll(Collection<? extends E> c)
1 | public boolean addAll(Collection<? extends E> c) { |
addAll(int index, Collection<? extends E> c)
1 | public boolean addAll(int index, Collection<? extends E> c) { |
contains(Object o)
1 | public boolean contains(Object o) { |
indexOf(Object o)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public int indexOf(Object o) {
int index = 0;
// 如果匹配对象为null,遍历LinkedList,使用==进行匹配,并返回位置;如果匹配对象不为null,遍历LinkedList,使用equals进行匹配,并返回位置
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++;
}
}
// 匹配不到,返回-1
return -1;
}
get(int index)
1 | public E get(int index) { |
get(int index)
流程:
- 判断定位是否越界
- 调用node方法,获取节点
- 返回节点的内容
remove(int index)
1 | public E remove(int index) { |
unlink(Node
x) 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
38E unlink(Node<E> x) {
// assert x != null;
// 获取节点的内容
final E element = x.item;
// 获取定位到的节点的后一个节点
final Node<E> next = x.next;
// 获取定位到的节点的前一个节点
final Node<E> prev = x.prev;
// 如果前一个节点为null,将头节点设置为后一个节点
if (prev == null) {
first = next;
} else {
// 如果前一个节点不为null,将前一个节点的下一个节点设置为定位到的节点的下一个节点
prev.next = next;
// 将定位到的节点的前一个节点设置为null
x.prev = null;
}
// 如果后一个节点为null,将尾节点设置为前一个节点
if (next == null) {
last = prev;
} else {
// 如果后一个节点不为null,将后一个节点的前一个节点设置为定位到的节点的前一个节点
next.prev = prev;
// 将定位到的节点的后一个节点设置为null
x.next = null;
}
// 将定位到的节点的内容设置null
x.item = null;
// LinkedList大小减1
size--;
// 操作次数加1
modCount++;
// 返回定位到的节点的原内容
return element;
}
remove(Object o)
1 | public boolean remove(Object o) { |
set(int index, E element)
1 | public E set(int index, E element) { |
队列相关方法
TODO
总结
通过这篇文章,我们对LinkedList进行了更深入的了解,在以后使用过程中,可以避免一些坑。
ps:关于队列的一些方法后续有空会进行添加。
欢迎关注博主其他的文章。