LinkedList源码解读
本文基于jdk16
1、LinkedList底层结构
(1)LinkedList底层维护了一个双向链表
(2)LinkedList中维护了两个属性first和last分别指向首节点和尾节点
transient Node<E> first;
transient Node<E> last;
(3)每一个节点都是一个Node对象,Node对象中又维护了prev、next、item三个属性,prev代表前一个节点,next代表后一个节点,item代表了具体的值,通过这三个属性实现双向链表
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;
}
}
(4)LinkedList元素的添加删除不是通过数组实现的,相对来说效率较高
(5)LinkedList在线程同步中是不安全的,因为其操作方法没有实现同步
2、源码解读
(1)初始化
通过上面的LinkedList linkedList = new LinkedList();调用无参构造创建LinkedList对象
public LinkedList() {
}
(2)添加元素
从上面linkedList.add(“张三”);进入以下代码
public boolean add(E e) {
//具体的添加操作
linkLast(e);
return true;
}
从上面的linkLast(e);进入以下代码
第一次添加元素,首先将新创建的节点赋值给last,然后在进入到if中,将新添加进来的节点赋值给first,
//e为要添加的元素
void linkLast(E e) {
//将linkedList的last赋值给l
final Node<E> l = last;
//创建新节点 prev为l即last 元素值为e next为null
final Node<E> newNode = new Node<>(l, e, null);
//将新创建的节点赋值给last
last = newNode;
//如果l即原来的last为空时,即LinkedList为空时进入if
if (l == null)
//LinkedList为空时first等于新创建的节点
first = newNode;
//当LinkedList不为空时
else
//原先的最后一个节点的下一个节点为新创建的节点
l.next = newNode;
//LinkedList的size加一
size++;
//被修改次数加一
modCount++;
}
非第一次添加元素,first不变,然后在进入else中,将新加入的节点的值赋值给原先最后一个节点的next
(3)删除元素(头元素)
从上面的linkedList.remove()进入以下代码
public E remove() {
//真正的操作方法
return removeFirst();
}
从上面的removeFirst()进入以下代码
public E removeFirst() {
//f为此时的头元素
final Node<E> f = first;
//如果f为null,则说明此时LinkedList中无元素,则抛出异常
if (f == null)
throw new NoSuchElementException();
//移除头元素方法
return unlinkFirst(f);
}
从上面的unlinkFirst(f)进入以下代码
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//element为头元素的值,因为移除元素需要返回被移除元素的值
final E element = f.item;
//next为头元素的下一个节点
final Node<E> next = f.next;
//将头元素的item、和next置为null
f.item = null;
f.next = null; // help GC
//将原先头元素的下一个元素赋值给头元素
first = next;
//如果next为空,则说明原LinkedList中只有一个元素,则也要将last置为空
if (next == null)
last = null;
//else代表了原LinkedList还存在元素
else
//此时的first = next 将next的前一个节点置为空
next.prev = null;
//linkedList长度减一
size--;
//被修改的次数加一
modCount++;
//返回被移除的元素的值
return element;
}
(4)修改元素(修改某个位置上的元素)
从上面的linkedList.set(1,“赵虎”);进入以下代码
public E set(int index, E element) {
//检查索引是否存在越界
checkElementIndex(index);
//获取到该索引所在节点
Node<E> x = node(index);
//被修改的节点的值,用于返回
E oldVal = x.item;
//修改节点的值
x.item = element;
//返沪被修改的节点值
return oldVal;
}
从上面的Node x = node(index);进入以下代码
//需要修改的值
Node<E> node(int index) {
// assert isElementIndex(index);
//如果index小于当前linkedList元素个数的一半进入if,从头开始找到该节点
if (index < (size >> 1)) {
//x为头节点
Node<E> x = first;
//这里可以这样理解,如index为1,则代表要找到头节点的后一个节点
for (int i = 0; i < index; i++)
x = x.next;
//返回索引值对应的节点
return x;
//如果index大于或等于当前linkedList元素个数的一半进入else,从尾开始找到该节点
} else {
//x为尾节点
Node<E> x = last;
//从最后一个节点开始往前找,找到index索引值对应的节点
for (int i = size - 1; i > index; i--)
x = x.prev;
//返回索引值对应的节点
return x;
}
}
(5)获取元素值
public E get(int index) {
//检查索引是否存在越界
checkElementIndex(index);
//此处的node(index)与上面修改元素获取节点操作相同 可查看上面的源码解释
return node(index).item;
}
3、ArrayList与LinkedList比较
ArrayList和LinkedList的比较
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组Object[] | 较低(因为需要数组扩容) | 较高 |
LinkedList | 双向列表 | 较高(通过链表进行追加) | 较低 |
在使用时该如何选择ArrayList和LinkedList:
1)如果需要进行改查的操作多,选择ArrayList;
2)如果需要进行增删的操作多,选择LinkedList;
3)一般来说,在程序中,我们使用更多的是查询,因此大部分情况下会选择ArrayList;