集合源码学习(五):LinkedList

时间:2022-09-30 17:59:59

何为LinkedList?

LinkedList本身是一个,由Java中的链表思想的数据结构对象,类似于链表,而在Java里面,没有指针,是通过引用来连接的。是一个链表,同时也实现了队列和栈的操作。另外,它也是可以随机访问的,只是和前两篇的ArrayList不同,它的随机访问是通过遍历从而获取特定元素的。接下来从特定方面来讲。

定义头:

/**
*
* 基于双向链表实现,允许所有元素包括null
* 在java中的链表又称为动态数组连接而成的链表。
* 实现不是同步的。
* 同样,它的iterator也是fail-fast的
*/

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

transient int size =
0;


transient Node<E> first;


transient Node<E> last;

Deque:支持两端元素插入和移除的线性集合。名称deque是“双端队列”的缩写
默认长度为0,并且保存有头节点和尾节点。

Node节点

  /**
* 内部类,也就是链表的节点信息,双向链表,即一个指向前一个节点,一个指向后一个节点。
* @author anla7856
*
* @param <E>
*/

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;
}
}

Node就是代表链表的一个节点,节点里存有item:信息,next:下一个节点,prev:前一个节点。

LinkedList的主要操作

1):插入节点

/**
* 作为首节点插入
*/

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++;
}

/**
*把元素e插入为数组末尾元素。
*/

void linkLast(E e) {
final Node<E> l = last;
//这一句已经把newnode的pre指针接上了。
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

在链表开头插入节点以及在链表尾上插入节点
其他的操作和此操作原理类似,例如有
void linkBefore(E e, Node succ) ,将e插入到succ的前面

2):删除节点

/**
* 把f的下一个节点作为头节点,返回f节点,可以类似于从头开始遍历
*/

private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

/**
* 把这个中间节点删除。
*/

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;
}

删除节点操作,如果e是边界节点,如unlinkFirst方法,则删除时,只需要把一个引用(prev或next)置为null即可,当然还要注意gc,把不要的节点置为null。
而如果删除e是中间节点,如unlink方法,则需要把e的前后两个节点串起来,也就是他们的prev和next方法需要连起来。

清空链表:

   public void clear() {
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}

把相应的位置引用置为null,便于垃圾回收。

3):随机访问

    /**
* 返回特殊顺序号的node节点,
* 实现思路,二分链表,然后遍历其中之一,
* 然后找到这个节点
*
*/

Node<E> node(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;
}
}

/**
* 随机设置值。
*/

public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}

所以在LinkedList的随机访问里面,其实是通过遍历整个集合,再根据特定判断条件(index,或者object)来找到特定的Node节点,1再对Node节点进行操作的。

ListItr迭代器

具体的和一般的迭代器差不多,里面有除了有访问方法next,previous之外,还有remove和add方法

    /**
* LinkedList的iterator,注意有fast-fail
* fast-fail中,只有遍历(访问)的时候才会检查ConcurrentModificationException异常
* 而在iterator中删除或者增加时,是不会检查的,因为此时,expectedModCount会自增,而modCount也会++
* 所以二者还是相等。
*/

private class ListItr implements ListIterator<E> {
...省略

除此之外,LinkedList还有一个能够以相反顺序遍历集合的DescendingIterator,文章开头讲了LinkedList具有双向队列功能,所以当然也具有从后往前遍历利用prev引用从后往前遍历的功能。
而里面的实际操作,则是通过ListItr来实现的。

   /**
* 能够以相反的顺序遍历的iterator,主要代码也是通过ListItr实现的。
*/

private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}

LLSpliterator

当然,LinkedList里面也有分割迭代器,大体的和其他集合类的迭代器相似,当然,由于是以链表的方式实现,所以数据量可能很大,所以在分割表获取迭代器方面,LLSpliterator迭代器有两个额外字段:

        static final int BATCH_UNIT = 1 << 10;  // 批处理数组的一次增加长度
static final int MAX_BATCH = 1 << 25; // 批处理数组最大长度,即a的

LLSpliterator也具有fast-fail特性,另外,在trySplit方法,也与其他的集合不同:

        /**
* 只有这个类返回是父类的spliterator,前几天看得两个都是直接返回自定义的linkedlist
* @return
*/

public Spliterator<E> trySplit() {
Node<E> p;
int s = getEst();
if (s > 1 && (p = current) != null) {
int n = batch + BATCH_UNIT;
if (n > s)
n = s;
if (n > MAX_BATCH)
n = MAX_BATCH;
Object[] a = new Object[n];
int j = 0;
// 把linkedlist都装到a里面。
do { a[j++] = p.item; } while ((p = p.next) != null && j < n);
current = p;
batch = j;
est = s - j;
return Spliterators.spliterator(a, 0, j, Spliterator.ORDERED);
}
return null;
}

首先该方法返回的是父类的Spliterator而不是LLSpliterator,另一方面,当返回父类的迭代器后,进行操作的也有原LinkedList变为了Object[] a,所以最终相当于copy了一份LinkedList中数据,利用父类的Spliterator默认实现方法来进行并行迭代的操作。
虽然这样做了,但这样并不会出错,因为
1、LinkList没有变,则Object[] a 和原LinkedList相同,无所谓。
2、LinkList通过add或者remove等方法变了,那在进行tryAdvance和forEachRemaining就会抛出ConcurrentModificationException错误,因为是快速失败的。

总体来说,LinkedList实现比较简单的,但首先得熟练长或链表的操作,包括链表的建立(头插法,尾插法),删除,遍历,以及双向链表的结构特性等等。

感觉有收获喔^^