LinkedList简介(jdk1.8)
LinkedList是基于双向链表实现的。如下为LinkedList的继承体系结构:
1 | public class LinkedList<E> |
可以看到,LinkedList实现了Deque接口,Deque表示双端队列,即在两端都可以进行插入和删除的队列。Deque是一个比Stack和Queue功能更强大的接口,它同时实现了栈和队列的功能。Deque接口的部分方法如下:1
2
3
4
5
6
7
8
9
10
11// *** Queue methods ***
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
// *** Stack methods ***
void push(E e);
E pop();
从代码可以看出,Deque既可以用作后进先出的栈,也可以用作先进先出的队列。
与ArrayList一样,LinkedList也不是线程安全的,因此只能在单线程环境下使用。
属性
LinkedList有size、first、last三个属性:
1 | //LinkedList中元素的数量 |
Node
既然LinkedList是基于链表实现的,那就必须要介绍一下它的内部类Node:1
2
3
4
5
6
7
8
9
10
11private 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;
}
}
因为是双向链表,所以每个节点都包含前一个节点的指向与后一个节点的指向。
构造函数
无参构造函数
1
2public LinkedList() {
}有参构造函数
1
2
3
4public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
此方法先调用一个无参构造函数构造一个空列表,然后再将集合内的所有元素添加进去。
addAll
将集合内的所有元素加入到LinkedLiist中。
1 | public boolean addAll(Collection<? extends E> c) { |
1 | public boolean addAll(int index, Collection<? extends E> c) { |
此段代码中,succ表示后节点,pred表示前节点。
在进行了下标检查与长度检查后,首先判断要加入的元素是加入在末尾还是中间,如果在末尾,则succ应指向null,而pred应指向last,否则,succ应指向下标为index的节点,而pred指向该节点的前一个节点。这样,要插入的节点的前后节点就都有了,接下来就可以将要插入的节点的前后节点都连接好,从而完成插入操作。
这里有必要介绍一下取出指定位置的节点的方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Node<E> node(int index) {
// assert isElementIndex(index);
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;
}
}
与数组不同,链表无法直接通过下标找到指定的元素,而需要依次遍历。由于LinkedList是通过双向链表实现的,所以既可以从头也可以从尾开始遍历。为了提高效率,该方法先判断指定的位置index在链表的前半段还是后半段,从而决定从头还是从尾开始遍历。
linkFirst,linkLast,linkBefore
在介绍add方法与其它相关方法前,有必要先介绍一下这三个辅助方法: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
36private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
linkFirst、linkLast、linkBefore方法分别将元素加入到链表头部、链表尾部与链表中指定节点之前。
以linkFirst为例,先创建一个新的节点,并将first指向该节点。然后判断以前的first节点是否为null,如果为null,则说明之前链表中没有元素,应将last指向新节点,否则,将原first节点的prev指向新节点。
add,addFirst,addLast
介绍完上面三个辅助方法后,我们再来看看add相关的方法。
1 | public boolean add(E e) { |
由源码可以看到,add相关的代码都是直接调用上面介绍的辅助方法,十分简单。
unlink,unlinkFirst,unlinkLast
同样,在介绍remove及相关方法时,先介绍这三个辅助方法:
1 | E unlink(Node<E> x) { |
这三个方法也很好理解。以unlink方法为例,将要删除的元素的前后节点相连接,并且把要删除的节点的属性设为null以帮助垃圾回收机制回收,从而达到移除该节点的目的。最后,将要删除的节点的值返回。
remove,removeFirst,removeLast
接下来介绍移除链表中元素的几个方法。
1 | public E remove() { |
前两个方法比较简单,而对于remove(Object o)方法,要先判断对象是否为null,如果为null,则遍历链表找到值为null的节点,并调用unlink方法移除该节点,否则,同样遍历链表并用equals方法根据内容进行等值比较,如果找到值相等的节点,调用unlink方法将其移除。
1 | public E removeFirst() { |
这两个方法先判断链表中是否有元素,如果没有,则抛出异常,否则就调用辅助方法将其移除。
get,getFirst,getLast
1 | public E get(int index) { |
get(int index)方法在进行了下标检查后,直接通过node方法找到该节点并返回节点的值。而getFirst和getLast先判断first和last是否为null,如果不为null则返回节点的值,否则抛出异常。
set
set方法将替换链表中指定位置的节点的值。
1 | public E set(int index, E element) { |
该方法先判断index是否合法,然后获取到该下标的节点,并将该节点的值重新设置即可。
linkedList总结
- linkedList是通过双向链表实现的,因此删除效率很高,而查找效率很低,且不存在扩容问题。
- linkedList实现了Deque接口,因此既可以当作栈,也可以当作队列。
- 与ArrayList一样,linkedList也是非线程安全的,只能在单线程环境下使用。