基于Java8
ArrayList的优点在于对get和set的调用花费常数时间。缺点是新增和删除代价昂贵,除非在ArrayList的端操作。
那需求要经常新增和删除呢?为此Java提供了与之对应的LinkedList。LinkedList是双向链表实现的,它的优点在于,在已知变动位置的情况下新增和删除花费常数时间,缺点在于不利于索引get比较昂贵。
结构
LinkerList是由一个双向链表实现的,其中每个节点都可向上或向下索引。

//标记节点
transient LinkedList.Node<E> first; //头节点
transient LinkedList.Node<E> last; //尾节点
//存储数据的节点
private static class Node<E> {
E item;
LinkedList.Node<E> next;
LinkedList.Node<E> prev;
Node(LinkedList.Node<E> prev, E e, LinkedList.Node<E> next) {
this.item = e;
this.next = next;
this.prev = prev;
}
}
LinkedList使用静态内部类Node来存储数据,Node中的prev指向前一个节点的位置,next指向下一个节点的位置。为了利于实现引入了标记节点,其中first为头节点,last为尾节点。
增
增加节点常用的方法 add(E e),addLast(E e),addFirst(E e)和add(int index, E e).
add(E e)
//新增数据,默认尾部添加
public boolean add(E e) {
this.linkLast(e);
return true;
}
// 从尾部开始添加节点
void linkLast(E e) {
// 把尾节点数据暂存
LinkedList.Node prev = last;
// 新建新的节点,初始化入参含义:
// prev 是新节点的前一个节点,当前值是尾节点值
// e 表示当前新增节点,当前新增节点后一个节点是 null
final Node<E> newNode = new Node<>(prev, e, null);
// 新建节点追加到尾部
last = newNode;
//prev是尾节点,prev为空,链表也就是空的,就把新节点赋值给头节点
if (prev == null)
first = newNode;
//不为空则把前尾节点的下一个节点,指向当前尾节点。
else
prev.next = newNode;
//数组大小和结构更改 +1
size++;
modCount++;
//至此元素添加完毕。
}
从尾部追加节点比较简单,头部追加节点也是类似。指定位置添加则麻烦一些,需要遍历查找得到相应节点然后进行操作,其余也和上述方法类似。
//遍历查找节点
LinkedList.Node<E> node(int index) {
LinkedList.Node node;
int var3;
//采用二分法减少运算量,
//index小于size/2则从头节点往后遍历
//大于size/2则从尾节点向前遍历
if (index < this.size >> 1) {
node = this.first;
for(var3 = 0; var3 < index; ++var3) {
node = node.next;
}
return node;
} else {
node = this.last;
for(var3 = this.size - 1; var3 > index; --var3) {
node = node.prev;
}
return node;
}
}
删
常用的删除节点方法有 remove(int index),removeFirst(),removeLash()和remove(Object obj).删除是把要删除节点的prev,next和item指向null,让GC可以回收它。
//删除指定位置的Node
public E remove(int index) {
this.checkElementIndex(index);
//this.node在add方法中分析了。
return this.unlink(this.node(index));
}
//删除节点
E unlink(LinkedList.Node<E> node) {
Object item = node.item;
//得到删除节点的后继节点
LinkedList.Node next = node.next;
//得到删除节点的前驱节点
LinkedList.Node prev = node.prev;
if (prev == null) {
this.first = next;
} else {
//让前驱节点的next指向删除节点的后继节点
prev.next = next;
//让删除节点的prev指向null
node.prev = null;
}
if (next == null) {
this.last = prev;
} else {
//让后继节点的prev指向删除接节点的前区节点
next.prev = prev;
//删除节点的next指向null
node.next = null;
}
//删除节点的item指向null
node.item = null;
--this.size;
++this.modCount;
return item;
//至此remvoe完毕
}
尾部删除,头部删除和上述类似。
改
修改方法只有一个 set(int index, E e).
public E set(int index, E e) {
this.checkElementIndex(index);
//在add()已分析
LinkedList.Node node = this.node(index);
Object item = node.item;
node.item = e;
return item;
}
此方法比较简单。
查
查询方法有get(int index),getFirst(),getLast()和indexOf(Object obj).
//根据索引查找元素
public E get(int index) {
this.checkElementIndex(index);
//在add()已分析
return this.node(index).item;
}
indexOf方法也比较简单先遍历后比较,相等则返回索引。
LinkedList 查询是比较慢的,需要遍历查找。新增和删除比较快的,仅仅把节点的指向位置修改下就好了。
迭代器
LinkedList 要实现双向的迭代访问,但Iterator只支持从头到尾的访问,为此Java新增了一个迭代接口ListIterator,此接口提供了双向迭代方法。
// 迭代器
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;//上一次执行 next() 或者 previos() 方法时的节点位置
private Node<E> next;//下一个Node
private int nextIndex;//下一个Node的位置
//expectedModCount:期望结构更改次数;
//modCount:实际结构更改次数
private int expectedModCount = modCount;
...
}
/**
*从头到尾迭代
*/
//是否有下一个Node
public boolean hasNext() {
// 下一个Node的位置小于size就有
return nextIndex < size;
}
// 取下一个Node
public E next() {
//检查更改次数
checkForComodification();
if (!hasNext()){//再次检查
throw new NoSuchElementException();
}else{
lastReturned = next;
// next 成为下一个节点了,为下次迭代做准备
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
//检查实际更改次数和期望更改次数是否相同
final void checkForComodification() {
if (LinkedList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
*从尾至头迭代
*/
// 若上次节点索引大于 0,则表示还有节点
public boolean hasPrevious() {
return nextIndex > 0;
}
// 获取前一个节点
public E previous() {
checkForComodification();
if (!hasPrevious()){
throw new NoSuchElementException();
}else{
// next 为空:表示第一次迭代,取尾节点,或者上一次操作把尾节点删除掉了
// next 不为空:表示已经发生过迭代了,取前节点
lastReturned = next = (next == null) ? last : next.prev;
// 下一个Node位置变化
nextIndex--;
return lastReturned.item;
}
}
/**
*迭代器删除
*/
public void remove() {
checkForComodification();
// lastReturned 本次迭代需要删除的值,分以下空和非空两种情况:
// lastReturned 为空,说明调用者没有主动执行过 next() 或者 previos(),直接报错
// lastReturned 不为空,是在上次执行 next() 或者 previos()方法时赋的值
if (lastReturned == null){
throw new IllegalStateException();
}else{
Node<E> lastNext = lastReturned.next;
//删除当前节点
unlink(lastReturned);
// next == lastReturned :从尾到头递归顺序,并且是第一次迭代,并且要删除最后一个元素的情况下
// 这种情况下,previous() 方法里面设置了 lastReturned = next = last,所以 next 和 lastReturned会相等
if (next == lastReturned){
// 这时候 lastReturned 是尾节点,lastNext 是 null,所以 next 也是 null,这样在 previous() 执行时,发现 next 是 null,就会把尾节点赋值给 next
next = lastNext;
}else{
nextIndex--;
}
lastReturned = null;
//期望结构更改次数 +1;
expectedModCount++;
}
}
LinkedList是非线程安全的,官方建议使用“Collections.synchronizedList(new LinkedList(...))”虽然实现了线程安全,但是它的性能比较低,是通过给每个方法加上锁来实现的。
可存储所有元素包括null.