LinkedList
概述
LinkedList实现的接口List和Deque,实现了几乎所有的LIst接口操作,其中保存的数据也可以是null。在其内部也不有实现线程安全的方法,所以其也是线程不安全的,跟ArrayList一样,多线程编程时要考虑同步的问题。其定义了一个Node内部类,表示一个双向链表上一个个节点,其内部使用双向链表来实现数据存储的。
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;
}
}
成员变量
// 存储的元素个数
transient int size = 0;
// 指向双端链表的开头节点
transient Node<E> first;
// 指向双端链表的链尾节点
transient Node<E> last;
构造函数
其构造函数一个是空实现,一个是用另外一个集合进行初始化。
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
addAll() 方法
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);// 初始时size为0
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//检查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);// 找到index位置上的Node,做为要插入元素的后继,看下面的代码摘录
pred = succ.prev;//给前驱赋值
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)// 如果前驱是null,则说明要插入的node是队列的第一个
first = newNode;
else//
pred.next = newNode;
pred = newNode;// 将当前node设置为前驱,为下一次循环做准备
}
// 将新元素接入队列中
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;// 设置size
modCount++;
return true;
}
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) {
if (index < (size >> 1)) {// index在队列的前一半中
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {// index 在队列的后一半中
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
由上面的代码摘录可以看出,插入新元素时没有像ArrayList和Vector那样对内存进行复制,这样就提高了添加删除的执行效率,但是由于链表实现,需要进行寻址,在查找时不如ArrayList和Vector好样连续内存块效率高。
常用方法
添加新元素
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
//以前驱为添加前最后一个节点,后继为null创建一个新节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;// 链尾节点设置为新节点
if (l == null)// 如果原链尾节点为null,说明原链表为中无数据,新表头节点设置为新节点
first = newNode;
else
l.next = newNode;
size++;// 元素个数加1
modCount++;
}
add方法将新元素链接到链表的末尾,下面是一个将新元素插入到指定位置的重载add方法:
public void add(int index, E element) {
checkPositionIndex(index);// 检查index是否在有效位置
if (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;// 在链表头和尾之间(包括头尾)算是有效位置
}
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;// index位置上节点的前驱节点
// 创建新节点,新节点的前驱后继分别是,index位置的前驱和index位置上的节点
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
节点的删除
public E removeFirst() {
final Node<E> f = first;
if (f == null)// first为null,说明链表中无元素
throw new NoSuchElementException();
return unlinkFirst(f);// 将第一个节点从链表中移除
}
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;// 第一个节点的后继节点
// 将第一节点的内容和后继置null
f.item = null;
f.next = null; // help GC
first = next;// 将表头设置后原第一节点的一后继节点
if (next == null)// 如果后继为null,说明移除表头后,链表中已经元素了,将链尾置null
last = null;
else
next.prev = null;//做为新的表头,其前驱应该为null
size--;// 元素数减1
modCount++;
return element;
}
改变指定位置元素的值
public E set(int index, E element) {
checkElementIndex(index);// 检查index是否合法
Node<E> x = node(index);// 查找到index位置上的节点
E oldVal = x.item;
x.item = element;// 将节点上的元素设置为新元素
return oldVal;
}
其它方法
public boolean offer(E e)// 在链表尾添加新元素
public boolean offerFirst(E e) // 在链表头添加新元素
public boolean offerLast(E e)// 在链表尾添加新元素
public E peek()// 获取表头元素,但不删除
public E peekFirst()// 获取表头元素,但不删除
public E peekLast()// 获取表尾元素,但不删除
public E poll()// 获取表头元素,并删除之
public E pollFirst()// 获取表头元素,并删除之
public E pollLast()// 获取表尾元素,并删除之
总结
LinkedList是双向链表实现,其所有操作都是在双向链表的基本操作,总结下来其有如下特性
-
实现了列表和双端队列几乎所有的接口,容量无限,只要内存够大。
-
内部俱数据可以重复,可以为null
-
不是线程安全的类
-
效率上查询相较ArrayList慢,添加删除相对较快