LinkedList结构
LinkedList是一个实现了List接口和Deque接口的双向链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; 另外LinkedList实现了Cloneable接口和Serializable接口。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
LinkedList源码(Jdk 1.8)
LinkedList源码都比较简单,这里我没有把全部方法列出来,有需要的可以去直接阅读源码。
Node
在源码中,LinkedList的基本存储单元是节点Node,它是LinkedList内部的一个私有静态类,内部有三个属性,分别是前驱节点,当前节点内元素,后继节点。
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;
}
}
添加元素
add(E e)
将要添加的元素放入链表尾部。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last; //获取当前last节点l
final Node<E> newNode = new Node<>(l, e, null); //构造新节点,pre指向当前last节点
last = newNode; //last指向新节点
if (l == null) //如果l为空,头结点指向新节点,否则将l.next指向新节点
first = newNode;
else
l.next = newNode;
size++; //LinkedList长度大小+1
modCount++;
}
add(int index,E e)
在指定位置添加元素
public void add(int index, E element) {
//检查index是否在允许范围内,否则抛出IndexOutOfBoundsException
checkPositionIndex(index);
if (index == size) //满足条件则添加在链表尾部,否则插在指定Node之前
linkLast(element);
else
linkBefore(element, node(index));
}
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++;
}
Node<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;
}
}
addFirst()
将元素添加到链表头部
public void addFirst(E e) {
linkFirst(e);
}
private 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++;
}
查找元素
get(int index)
在对给定索引进行查找时,先比较索引与长度中间值,如果小于中间值,则从头节点向后遍历;如果大于中间值,则从尾节点向前遍历。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Node<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;
}
}
int indexOf(Object o)
根据对象得到索引位置。从头节点开始查找的对象,查找的对象允许null,没有则返回-1。
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;
}
int lastIndexOf(Object o)
与indexOf()
实现方式一样,不过它是从尾部开始查找。
删除元素
remove(Object o)
删除指定元素。从头节点遍历整个整个链表,直到删除指定对象,返回true;否则false。
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;
}
E 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; //将链表前置节点的后置节点指向next
x.prev = null;
}
if (next == null) { //链表最后一个节点
last = prev;
} else { //将链表后置节点的前置节点指向prev
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
总结
-
LinkedList在插入操作时,都需要新建一个节点,他允许null值插入,允许重复,相应的允许null值查找和删除。
-
LinkedList中每个节点保存了前置节点和后置节点的引用,所以保证了数据的有序性
-
LinkedList并没有进行线程安全的同步操作,所以他是非线程安全的。如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:
List list=Collections.synchronizedList(new LinkedList(...));
对比ArrayList
-
作为随机访问列表时,LinkedList先进行了二分比较,然后遍历size/2的链表。ArrayList可以直接根据索引进行随机访问。LinkedList随机访问效率非常低,尤其当数据量较大时更是如此。
-
LinkedList的插入或者删除元素操作效率都比较高,效率高指的是在插入和删除时,只需要改变Node节点中属性,而且不需要扩容操作。但是在插入和删除指定位置时,需要先进行第1步,查找到指定位置。
ArrayList插入或者删除元素时的效率取决于索引与数组末端的距离。距离越远,效率越低,相反的,如果所有插入和删除操作都在数组末端,过程中需要拷贝的数组很小,那么效率会优于LinkedList,另外如果不能确定数组的长度而导致频繁扩容操作,那么效率会非常低。