声明:本文为作者原创,请勿转载,如果转载请注明转载地址。
文章目录
LinkedList底层源码分析1. 数据域2. 构造函数3. 在链表中添加一个集合3.1 addAll(c)3.2 addAll(size, c)
4. 在链表的尾部添加元素4.1 add(E e)4.2 linkLast(E e)
5. 在链表的指定位置添加元素5.1 add(int index, E element)5.2 node(int index)5.3 linkBefore(E e, Node succ)
6. 判断链表中是否包含某一个元素6.1 contains(Object o)6.2 indexOf(Object o)
7. 从链表中删除指定元素7.1 remove(Object o)7.2 unlink(Node x)
8. 从链表中删除指定索引处的元素8.1 remove(int index)
9. 查询index处的节点值9.1 get(int index)9.2 node(index).item
LinkedList底层源码分析
先来看下双向链表的删除节点、添加节点、遍历节点的过程:
添加节点
删除节点:
查询节点:
1. 数据域
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque
<E>, Cloneable
, java
.io
.Serializable
{
transient int size
= 0;
transient Node
<E> first
;
transient Node
<E> last
;
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
;
}
}
}
2. 构造函数
LinkedList提供了两个构造器,ArrayList比它多提供了一个通过设置初始化容量来初始化类。LinkedList不提供该方法的原因:因为LinkedList底层是通过链表实现的,每当有新元素添加进来的时候,都是通过链接新的节点实现的,也就是说它的容量是随着元素的个数的变化而动态变化的。而ArrayList底层是通过数组来存储新添加的元素的,所以我们可以为ArrayList设置初始容量(实际设置的数组的大小)。
public LinkedList() {
}
public LinkedList(Collection
<? extends E> c
) {
this();
addAll(c
);
}
3. 在链表中添加一个集合
3.1 addAll©
public boolean addAll(Collection
<? extends E> c
) {
return addAll(size
, c
);
}
3.2 addAll(size, c)
public boolean addAll(int index
, Collection
<? extends E> c
) {
checkPositionIndex(index
);
Object
[] a
= c
.toArray();
int numNew
= a
.length
;
if (numNew
== 0)
return false;
Node
<E> 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
);
if (pred
== null
)
first
= newNode
;
else
pred
.next
= newNode
;
pred
= newNode
;
}
if (succ
== null
) {
last
= pred
;
} else {
pred
.next
= succ
;
succ
.prev
= pred
;
}
size
+= numNew
;
modCount
++;
return true;
}
总结:
(1) 先将传入的集合转换为数组
(2) 定义两个变量pred、succ并初始化,其中succ指向要插入的节点,pred指向要插入节点的前驱节点;如果要插入的节点为链表的尾部,就让pred指向last,succ指向null。如果要插入的节点不在链表的尾部,就让succ指向要插入的节点,pred指向succ.prev。之所以定义这两个节点变量一是为了遍历链表而是为了防止链表断掉。
(3) 遍历集合,每次遍历将集合中的元素创建为节点,该newCode.prev指向perd,next指向null。如果原来链表为null,将让当前节点为头结点,让first指向该节点。如果原来的链表不为null,就让pred.next指向newCode。同时让pred后移。
(4) 现在要做的是把整个链表连接上,分两种情况,如果要插入的节点位于链表的尾部,那么succ=null,此时让last指向pred即可。如果不位于尾部,需要让pred.next = succ,succ.prev = pred
因为是集合,因此整体的效果,就是下面图:
4. 在链表的尾部添加元素
4.1 add(E e)
public boolean add(E e
) {
linkLast(e
);
return true;
}
4.2 linkLast(E e)
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
++;
}
总结:在链表的尾部添加一个元素
(1) 因为尾节点不能动,定义一个节点变量l指向尾节点
(2) 将添加的元素创建为节点对象newCode,该节点的prev指向l,该节点的next指向null,同时让last指向新添加的节点,如果链表为null,就让该节点作为头结点,同时让first也指向该节点。否则让l.next=newCode
由此可见,链表在节点尾部添加节点效率是非常高的,因此只需要找到last指向的尾部节点,让后将待添加的节点放在该节点后面即可。
5. 在链表的指定位置添加元素
5.1 add(int index, E element)
public void add(int index
, E element
) {
checkPositionIndex(index
);
if (index
== size
)
else
linkBefore(element
, node(index
));
}
5.2 node(int index)
Node
<E> node(int 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
;
}
}
5.3 linkBefore(E e, 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
++;
}
总结:
(1) 如果待插入的节点位于链表的尾部,直接调用linkLast(element)方法。
(2) 如果待插入的节点位于链表的头部或中间(此时succ一定不为null,如果为null就说明是链表的尾部),调用linkBefore(E e, Node succ)方法。这个方法传入两个变量:e代表待插入的节点元素,succ代表待插入的位置当前指向的节点。
首先找到要插入位置处节点succ的前驱节点pred,然后创建一个节点对象newCode,让该节点的prev=pred,next=succ同时让succ.prev = newCode;
如果pred=null,说明要插入的位置为链表的头部,就让当前节点作为头结点,让first=newCode,否则pred.next = newNode;
对于链表添加元素的效率比较高,因为只需要遍历链表找到待插入节点的位置,然后将节点插入到该位置即可,node(index)方法的作用就是查找指定位置处的节点,在查找的过程中,并不是从头向后直接查找指定位置处的节点,如果index<链表长度的一半,就从头节点开始查,如果index>=链表的长度的一半就从尾节点开始查。LinkedList是双向链表。
6. 判断链表中是否包含某一个元素
6.1 contains(Object o)
public boolean contains(Object o
) {
return indexOf(o
) != -1;
}
6.2 indexOf(Object o)
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;
}
总结:
因为链表中可以存放null值,因此判断链表中是否包含某个元素时需要分为两种情况,待查找的元素是否为null。返回的是链表中第一次出现该元素的位置,没有找到就返回-1。
7. 从链表中删除指定元素
7.1 remove(Object o)
public boolean remove(Object o
) {
if (o
== null
) {
for (Node
<E> x
= first
; x
!= null
; x
= x
.next
) {
if (x
.item
== null
) {
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;
}
7.2 unlink(Node x)
E
unlink(Node
<E> x
) {
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
;
}
从链表中删除指定的元素,要分为两种情况,就是要删除的元素为null值,以及要删除的元素不为null值。原理上都是通过遍历链表找到待删除的元素,然后调用unlink(Node x)方法删除该元素:
删除元素的时候目的是:将该节点与他的前驱节点连接的两根线断掉,将该节点与他的后继节点连接的两根线断掉
(1)将该节点与他的前驱节点连接的两根线断掉:
如果待删除的节点为头节点,让first=next;否则,让prev.next = next,x.prev = null
(2) 将该节点与他的后继节点连接的两根线断掉:
如果待删除的节点为尾节点,让last=prev;否则,让next.prev = prev;x.next = null;
8. 从链表中删除指定索引处的元素
8.1 remove(int index)
public E
remove(int index
) {
checkElementIndex(index
);
return unlink(node(index
));
}
9. 查询index处的节点值
9.1 get(int index)
public E
get(int index
) {
checkElementIndex(index
);
return node(index
).item
;
}
9.2 node(index).item
Node
<E> node(int 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
;
}
}
实现队列:
public E
peek() {
final Node
<E> f
= first
;
return (f
== null
) ? null
: f
.item
;
}
public E
element() {
return getFirst();
}
public E
poll() {
final Node
<E> f
= first
;
return (f
== null
) ? null
: unlinkFirst(f
);
}
public E
remove() {
return removeFirst();
}
public boolean offer(E e
) {
return add(e
);
}
继承了Dequ接口,可实现双端队列:
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
;
}
public E
pollFirst() {
final Node
<E> f
= first
;
return (f
== null
) ? null
: unlinkFirst(f
);
}
public E
pollLast() {
final Node
<E> l
= last
;
return (l
== null
) ? null
: unlinkLast(l
);
}
public void push(E e
) {
addFirst(e
);
}
public E
pop() {
return removeFirst();
}