LinkedList方法解析及实现原理
LinkedList
为一个链表类型的List列表,列表在频繁的插入和删除列表元素的时候,使用LinkedList
比使用ArrayList
将更为高效。
目录
LinkedList
的构造函数很简单,没有什么额外说明的必要。但是LinkedList
中的一些参数可能说明一下会更好。
在引入参数之前,我们需要先简单介绍一下LinkedList
的内部静态类Node
结点。Node
的源码不多,所以我想直接贴出来看下。
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;
}
我们可以看到一个Node
对象中包含了两个Node
对象,一个是前结点prev
,一个是后结点next
,同时还有结点的值item
。
而LinkedList
中维护了两个结点Node
,首结点first
,尾结点last
。好了,介绍到这里我们差不多就可以开始我们LinkedList
的一些方法讲解了。
add(int index, E element)
checkPositionIndex(index); //边界检测
if(index == size)
linkLast(element); //链表尾部插入
else
linkBefore(element, node(index)); // 索引index前插入
此添加元素方法首先会先调用checkPositionIndex(index)
进行边界检测。对index
的值进行>=0 && <=size
的边界检测。如果index
在合理范围内,那么就会开始插入操作。但在插入操作的时候会进行一次所添加的index
是否为size
,即是否想把元素值插入到列表的最后一项。因为其实大部分操作都是从尾部开始加值。如果加入最后一项,那么调用包内方法linkLast(E e)
。
final Node<E> I = last;
final Node<E> newNode = new Node<>(I, e, null); // 新建一个结点,该结点的前部指向LinkedList的尾部结点last
last = newNode; // 将新的结点声明为链表的尾部,即将newNode赋值给last
if(I == null)
first = newNode; // 如果链表没有结点,那么就将newNode作为链表尾部的同时也作为链表的首部
else
I.next = newNode; // 如果链表有结点,在链表尾部加入新结点。
size++;
那么当index
不为size
的时候,即不是从尾部插入的时候,那么就需要在某一项前插入,可以调用linkBefore(E e, Node<E> succ)
。讲解这个方法前,我想先讲解下上述源码中node(int index)
这个函数。
初看这个函数可能很简单,通过索引index
来查到链表中的结点。确实是这样的,但是内部的实现方法呢,之前我们在ArrayList
中是通过正序遍历,但是这里就不是了。因为ArrayList
实质是一个数组,数组的索引是很快的。而我们这里的LinkedList
却是一个一个的结点。所以只能依次遍历过去,但如果从首部开始一个个遍历的话效率又太低,但是我们又无法通过数组那样直接通过索引找到,只能通过一个节点的下一个节点的这样寻找。所以源码中采用了一种二分查找的方式。node(int index)
先判断index
是否在链表的前半部分size >> 1
,如果是的话那就正序开始遍历。否则在链表后半部分的话,那就从尾部结点开始逆序遍历。
好了,我们了解了链表中通过index
如何查找到结点,那现在就可以开始介绍linkBefore(E e, Node<E> succ)
了。先贴源码如下:
final Node<E> pred = succ.prev; // succ为链表中处在index处的结点。
final Node<E> newNode = new Node<>(pred, e, succ); //新建一个元素为e的结点,其头部指向succ的前一个结点,尾部指向succ.
succ.prev = newNode; //插入结点,将新节点指向index处结点的头部
if(pred == null)
first = newNode; // 如果succ之前没有结点,那么把newNode插入到succ之前,那么newNode自然而然成为了链表的头结点了
else
pred.next = newNode; //否则应该将newNode查到前一个结点pred的尾部。
size++;
该方法总体思路就是,将元素E element
插入的时候,新建一个element
的结点,然后将处于index
的结点和之前的结点断开。然后再将新建结点收尾依次对应插入进去。
add(E e)
该方法其实就是在链表的尾部加入元素E,也即调用上述所说的linkLast(E element)
方法。
get(int index)
从链表中根据索引index来获取元素,其实实现原理我们之前在add(int index, E element)
也有说过,通过index
调用node(int index)
拿到处于索引index
处的结点。然后再返回该结点的item
即可。
remove(int index)
链表移除index
处的元素,先通过node(int index)
找到位于index
处的结点,然后通过unlink(Node<E> e)
来解除该结点的绑定。
final E element = x.item;
final Node<E> next = x.next;
final Node<e> prev = x.prev;
if(prev == null){
first = next; //如果前结点为空的话,那么next结点就是第一个结点了,所以赋值给first。
}else{
prev.next = next; //否则prev的尾部指向next结点
x.prev = null; // x结点的首部不再指向prev,所以赋值null,有助GC。
}
if(next == null){
last = prev; // 如果x之后指向的结点next为空的话,那么说明x后面没有结点了,此时又移除x结点,所以链表尾部结点就是prev结点了。
}else{
next.prev = prev; //否则next的首部应该指向prev,之前是指向x,但因移除x,所以现在指向prev
x.next = null;
}
x.item = null; // item置null,有助GC。
size--;
modCount++;
return element;
remove(Object o)
该方法传入参数是Object o
不是索引index
了。所以无法通过之前的二分的方法去一个个遍历到Objdec o
,因此只能通过正序遍历在LinkedList
中一个个找到元素Object o
。并调用unlink(Node<E> x)
将其移除。贴下源码让大家看下。
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;
小结: LinkedList
维护了一个链表结构,适合插入和删除元素,查找的效率低。和ArrayList
一样,LinkedList
同样是非线程安全。