概述
LinkedList 底层的实现是双向链表,因而它改查的效率较低,而在增删方面,由于不用进行数据的整体移动,所以在中间进行增删时具有较高的效率。
下面就从源码入手,对 LinkedList 的增删改查作进一步的了解。
主要属性
1 | // 元素个数 |
构造方法
有两个构造方法,首先看默认构造方法:1
2public LinkedList() {
}
默认方法为空,也就是说在创建 LinkedList 时没有初始化任何属性。
另一构造方法传入一个 Collection 对象,根据该对象的元素进行初始化:1
2
3
4public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
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;
}
}
可以看出,这是一个双向链表。
基本操作
增
主要通过 add 系列方法增加元素,其中 add(E e)
和 addLast(E e)
在链表尾部添加元素,addFirst(E e)
在链表头部添加元素,add(int index, E element)
在链表的指定位置添加元素。
先看下 add(E e)
:
add(E e)
1 | public boolean add(E e) { |
1 | void linkLast(E e) { |
插入过程比较简单,如果链表为空,则新节点同时成为头结点和尾结点;如果链表不为空,则新节点插入到原尾节点后面,成为新的尾节点。
这是插入到链表尾部的过程,插入到链表头部的过程类似,就不多说了。下面分析插入到中间位置的情况,看 add(int index, E element)
:
add(int index, E element)
1 | public void add(int index, E element) { |
其中 node 方法
根据索引得到相应的节点:
node(int index)
1 | Node<E> node(int index) { |
这里利用到了双向链表的性质,根据 index 的值更接近哪头,来决定是要从前往后找还是从后往前找。
回到 add 方法,再看下 linkBefore 方法
:1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
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++;
}
该方法将新节点插入到第 index 个节点前。
删
主要通过 remove 系列方法删除元素,其中 remove()
和 removeFirst()
方法在删除链表头部元素,removeLast()
删除链表尾部元素,remove(int index)
删除指定位置的元素。
先来看下 remove()
:
remove()
1 | public E remove() { |
1 | public E removeFirst() { |
1 | private E unlinkFirst(Node<E> f) { |
该方法删除并返回第一个元素,如果集合中没有元素会抛出异常。
removeLast()
则是删除最后一个元素,原理类似,就不多说了。
接下来看下删除指定位置元素的 remove(int index)
:
remove(int index)
1 | public E remove(int index) { |
在得到要删除的节点后,调用 unlink 方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) { // 删除的是头结点
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) { // 删除的是尾结点
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
从链表中删除该节点,并返回元素。
改
要修改某个位置的元素,可以调用 set(int index, E element)
:
set(int index, E element)
1 | public E set(int index, E element) { |
找到要修改的节点后,更新元素并返回旧的值。
查
查询元素通过 get 系列方法,其中 getFirst()
得到第一个元素的值,getLast()
得到最后一个元素的值,get(int index)
得到指定位置的元素值。
源码也比较好理解:
getFirst() 和 getLast()
1 | public E getFirst() { |
1 | public E getLast() { |
通过读取头结点和尾结点即可得到相应元素,注意如果集合为空会抛出异常。
get(int index)
1 | public E get(int index) { |
还是通过 node 方法得到相应节点,从而得到所要查找的元素。
总结
- 底层使用双向链表,可以从两端进行插入和删除元素,所以可以作为栈、队列、双向队列使用。
- 通过索引查找节点的时候,如果索引在前半部分,就从前往后找,如果索引在后半部分,就从后往前找,提高效率。
- 在链表中间进行增删改查时,都需要根据索引进行查找操作。
- 在进行增加和删除操作时,modCount 都会增加。