1 特点
- 有序的,可以存储重复值和null值。
- 底层是双向链表实现的,线程不安全,在进行任何位置的增删操作花费的时间都一样,不需要搬运数据,效率高,但是在查询的时候要移动指针,查询慢,不适合查询。链表不会涉及到扩容问题。
- ArrayList 对空间的消耗是要低于LinkedList,因为LinkedList要维护前后指针,还要对数据进行封装
2 源码分析
2.1 接口的定义和属性
/**
*双向链表List,实现了 List 和 Deque接口,实现了list 接口的所有操作,并且允许所有的值,包括null
*所有操作都符合对双向链表的预期,关于index(下标或者是索引)的操作都会从头或者从尾部遍历整个链表
*/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
/**
* 集合的长度,表示LinkedList 存储了多少元素
*/
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.2 构造方法
//创建一个空的LinkedList
public LinkedList() {
}
/**
* 创建一个包含特定集合全部元素的list ,元素的顺序和特定集合的遍历顺序一致
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
2.3 add(E e)
public boolean add(E e) {
linkLast(e);
return true;
}
//添加元素e为最后一个元素
void linkLast(E e) {
final Node<E> l = last;
// 添加e 到list 的尾部,那么此时的last 则成为e 的前置,那么e 的后置则是null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
// last 为null 意味着是空表,此时e(newNode) 则成为first
first = newNode;
else
// 否则 e(newNode) 则成为 last 的后置
l.next = newNode;
size++;
modCount++;
}
2.4 add(int index, E element)
LinkedList插入效率高是相对的,因为它省去了ArrayList插入数据可能的数组扩容和数据元素移动时所造成的开销,但数据扩容和数据元素移动却并不是时时刻刻都在发生的。
//在指定位置插入指定的元素
public void add(int index, E element) {
// 检测下标的合法性
checkPositionIndex(index);
// 则判断是不是添加到集合尾部
if (index == size)
// 当index=size 的时候则是添加到集合的尾部
linkLast(element);
else
// 如果不是添加到最后,这个里通过node(index) 方法得到这个位置当前的元素
linkBefore(element, node(index));
}
// 检测下标的合法性
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
// 是返回特定位置的节点,链表中的节点,所以不可能为空
Node<E> node(int index) {
// 判断是从头开始遍历还是从尾部开始遍历,如果index<size/2 则从头遍历,否则从尾部开始遍历
//越靠近中间的元素,调用get(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;
}
}
//在一个非空节点前插入一个元素,succ 是传入的当前位置上已经存在的元素,接下来要做的就是将e 和 succ 连接起来
void linkBefore(E e, Node<E> succ) {
// 将e节点插入到succ 节点前
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
// 如果pree是null ,则succ就是first节点,那么e 就要代替succ 成为新的first
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
2.5 get(int index)
随机读取元素不是LinkedList所擅长的,读取效率比起ArrayList也低得多,因为LinkedList需要遍历
public E get(int index) {
//检测下标是否合法,与add方法中isPositionIndex条件不同,这里的index必须小于size
checkElementIndex(index);
// 返回特定位置的节点
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
// 是返回特定位置的节点,链表中的节点,所以不可能为空
Node<E> node(int index) {
// 判断是从头开始遍历还是从尾部开始遍历,如果index<size/2 则从头遍历,否则从尾部开始遍历
//越靠近中间的元素,调用get(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;
}
}
2.6 removeFirst()和removeLast()
//移除list 中的第一个元素
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//取消first 的连接
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
//移除first节点
f.item = null;
f.next = null;
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
//移除list 中的最后一个元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//取消last的连接
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
//移除last节点
l.item = null;
l.prev = null;
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
2.7 remove(Object o) 和 remove(int index)
思路相似,都是先找到要删除的节点,remove(Object o)方法遍历整个集合,通过 == 或 equals方法进行判断;remove(int index)通过node(index)方法。
//删除某个对象
public boolean remove(Object o) {
//遍历LinkedList,找到要删除的节点
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;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
//删除某个位置的元素
public E remove(int index) {
checkElementIndex(index);
//遍历LinkedList,先找到指定位置的节点,然后再删除
return unlink(node(index));
}