Java 集合体系之 LinkedList 源码分析

前言

在上一篇文章中我们分析了 ArrayList 的源码,知道了原来 ArrayList 内部是由数组来维护的。这篇文章再来分析分析 LinkedList 的源码。为了看起来方便,这篇会对源码进行拆分分析。分析之前,先来大概了解下 LinkedList。

LinkedList 的继承结构如下:

《Java 集合体系之 LinkedList 源码分析》

LinkedList 是 List 和 Deque 接口的双向链表实现。实现了所有可选的列表操作,并允许所有元素(包括 Null)。

需要注意的是,LinkedList 并不是同步的,如果有多个线程同时访问 LinkedList,并且至少有一个线程修改了集合,则必须在外部进行同步。如在创建时通过 Collections.synchronizedList() :

Collections.synchronizedList(new LinkedList<>());

官方文档:https://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html

源码分析

成员变量

transient int size = 0;// 集合的长度

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;// 指向第一个节点

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;// 指向最后一个节点

可以看到 LinkedList 就只有三个成员,并且都是 transient 修饰的,表示其不可被序列化。 这里的 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;
    }
}

可以看到,节点中定义了三个成员,分别存储的是上一个节点、节点中存储的元素、下一个节点。这也说明,其是一个双向节点。

构造方法

/**
 * 构造一个空的列表
 */
public LinkedList() {
}
/**
 * 构造一个包含指定集合的元素的列表(按照集合的迭代器返回的顺序)
 *
 * @param  c 其元素将被放入该列表的集合
 * @throws NullPointerException 如果指定的集合为 null
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

LinkedList 就只有两个构造,其中的 addAll() 方法会在下面具体分析是如何实现的。

添加方法

public boolean add(E e)
public void add(int index, E element)
public boolean addAll(Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c)
public void addFirst(E e)
public void addLast(E e)
public void push(E e)
public boolean offer(E e)
public boolean offerFirst(E e)
public boolean offerLast(E e)

add(E e)

/**
 * 将指定的元素追加到集合的末尾
 * 这个方法相当于:addLast() 方法
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

addLast(E e)

public void addLast(E e) {
    linkLast(e);
}

可以看到实际上都调用的是 linkLast()方法:

/**
 * 链接 e 作为最后一个元素(添加指定元素到末尾)
 */
void linkLast(E e) {
    final Node<E> l = last;// 获取尾部节点
    // 创建一个新的节点对象,头部指向的是最后一个节点,当前元素是 e,尾部没有指向
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;// 将最后一个节点指向新创建的节点
    if (l == null)// 如果“前任”尾部节点是空的,那么表示新创建的节点是第一个创建的节点
        first = newNode;// 将头部的节点指向新创建的节点
    else
        l.next = newNode;// 上一个节点的下一个节点指向新创建的节点
    size++;// 长度 +1
    modCount++;// 修改次数 +1
}

addFirst(E e)

/**
 * 在集合的头部添加指定的元素
 *
 * @param e 要添加的元素
 */
public void addFirst(E e) {
    linkFirst(e);
}
/**
 * 链接 e 作为第一个元素(添加指定元素到头部)
 */
private void linkFirst(E e) {
    final Node<E> f = first;// 获取头部节点
    // 创建新节点,头部没有指向,当前元素为 e,尾部指向的是头部节点
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;// 头部节点为新建节点
    if (f == null)// 如果“前任”头部节点是空的,那么表示新建的节点是第一个节点
        last = newNode;// 尾部节点也是新建的节点
    else
        f.prev = newNode;// 后一个节点的头部指向的是新建节点
    size++;// 长度 +1
    modCount++;// 修改 +1
}

add(int index, E element)

/**
 * 在集合的指定位置插入指定的元素
 *
 * @param index 指定元素要插入的索引
 * @param element 要插入的指定元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    // 校验 index 是否在集合长度的范围内
    // 如果不是则抛出 IndexOutOfBoundException 异常
    checkPositionIndex(index);

    if (index == size)// 如果插入元素的位置是末尾
        linkLast(element);// 那么就直接调用添加到末尾的方法(效率高)
    else
        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(int index):

/**
 * 返回指定位置处的(非空)节点 
 */
Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {// 如果指定位置小于了集合长度的一半,那就分一半进行遍历查找(提高了效率)
        Node<E> x = first;// 获取头部节点,以用于挨个获取下一个节点
        for (int i = 0; i < index; i++)// 从 0 开始,一直遍历到 index 处的节点(查找起来很苦逼... 没索引就只能挨个遍历了...)
            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
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;// 获取 succ 的前一个节点
    // 创建要插入元素 e 的新节点(头部指向上 succ 的前一个节点,尾部指向 succ)
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;// succ 的前一个节点指向新插入的节点
    if (pred == null)// 如果 succ 的前一个节点是空的
        first = newNode;// 那么插入的新节点就是第一个节点
    else
        pred.next = newNode;// 否则上个节点的下个节点就指向的是新节点
    size++;// 长度 +1
    modCount++;// 修改 +1
}

addAll(Collection c)

/**
 * 按照由指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到当前集合的末尾
 */
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
#### addAll(int index, Collection c)

/**
 * 从指定的位置开始,将指定集合的所有元素插入到当前集合
 */
public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);// 校验 index 是否在集合长度的范围内

    Object[] a = c.toArray();// 转成数组
    int numNew = a.length;// 获取长度
    if (numNew == 0)// 如果长度是 0,则直接返回 false,表示当前集合结构并没有被修改
        return false;

    Node<E> pred, succ;// 定义两个节点,分别表示要插入位置的上一个和下一个节点
    if (index == size) {// 如果要插入的位置正好是末尾
        succ = null;// 要插入位置的下一个节点为 null
        pred = last;// 要插入位置的上一个节点为尾部节点
    } else {
        succ = node(index);// 根据 index 查找到 succ 节点
        pred = succ.prev;// pred 为 succ 的上一个节点
    }

    for (Object o : a) {// 循环遍历要插入集合元素的所有元素
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);// 创建要插入的新节点,并指定上一个节点为 pred
        if (pred == null)// 如果 pred 为 null,那么表示要插入的节点前面木有节点
            first = newNode;// 那么头节点则就是插入的新节点
        else
            pred.next = newNode;// 否则 pred 的下个节点则就是新节点
        pred = newNode;// pred 往后移一位
    }
    // 上面代码执行完毕,数据全部插入了,但是和尾部还没有链接起来
    if (succ == null) {// 如果 succ 为 null
        last = pred;// 说明要插入的位置就是尾部了,尾节点就是 pred (也就是最后插入的节点)
    } else {// 否则,让 succ 和最后插入的节点:pred 链接起来
        pred.next = succ
        succ.prev = pred;
    }

    size += numNew;// 增加长度
    modCount++;// 修改次数 +1
    return true;// 返回true,表示结构修改了
}

push(E e)

/**
 * 将指定元素推到栈顶(也就是在列表的前面插入元素)
 */
public void push(E e) {
    addFirst(e);
}

offer(E e)

/**
 * 将指定元素添加到尾部
 */
public boolean offer(E e) {
    return add(e);
}

offerFirst(E e)

/**
 * 在此列表的前面插入指定元素
 */
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

offerLast(E e)

/**
 * 在此列表的后面插入指定元素
 */
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

至此,添加方法都分析完毕了,大概总结下:

我们每存储了一个元素,都会 new 一个新的 Node 对象,Node 对象中维护了 pred、item、next 三个变量,其中元素存储在 item 中,pred 和 next 则分别指向的是当前节点的上个和下个节点。

由此可见,相对于 ArrayList 的底层数组实现,该实现因为还维护了 prev 和 next,所以占用空间相对于ArrayList 要多。

并且,当直接在头部和尾部增加数据时,由于只需要让相邻的节点进行修改,所以相对于 ArrayList 需要对后续的数据全部进行移动来说,效率较高。

但是如果是在某个位置插入数据,则效率就不如直接在头尾部添加数据效率要高了,前者的时间复杂度是 O(n),后者的时间复杂度则是 O(1)。这是因为,当是在某个位置插入数据时,还需进行遍历查找要插入的位置节点。查找也是如此,也需要循环遍历。

删除方法

public E remove()
public E remove(int index)
public boolean remove(Object o)
public E removeFirst()
public E removeLast()
public boolean removeFirstOccurrence(Object o)
public boolean removeLastOccurrence(Object o)
public E pop()
public E poll()
public E pollFirst()
public E pollLast()
public void clear()

remove()

/**
 * 移除列表中的第一个元素并返回
 */
public E remove() {
    return removeFirst();
}

可以看到实际调用的是 removeFirst() 方法:

removeFirst()

/**
 * 从此列表中移除并返回第一个元素
 */
public E removeFirst() {
    final Node<E> f = first;// 获取头部节点
    if (f == null)// 如果是空的,表示集合中没有数据
        throw new NoSuchElementException();// 抛出异常
    return unlinkFirst(f);// 有数据,调用 unLinkFirst() 方法
}

removeFirst() 方法实际只校验了下是否是空的,实际移除的操作是由 unlinkFirst() 方法来作的:

// 这是一个私有方法,传入的参数 f,必须是非空的 first (也就是头节点)
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;// 将第一个元素置为 null
    f.next = null; // help GC 下一个节点的指向也置为 null,加速 GC 回收
    first = next;// 现在头节点就是原先头节点的下个节点了
    if (next == null)// 如果下个节点是空的话,那么表示集合中没有其他元素了
        last = null;// 尾节点也置为 null
    else
        next.prev = null;// 否则,头节点的上个节点是 null
    size--;// 长度 -1
    modCount++;// 修改次数 +1
    return element;// 返回被删除节点中的元素
}

remove(int index)

/**
 * 删除此列表中指定位置的元素,并返回被删除的元素
 */
public E remove(int index) {
    checkElementIndex(index);// 校验传入的索引是否在集合的范围内
    return unlink(node(index));// 首先查找对于的位置的节点,然后调用了 unlink() 方法
}
  • unlink(E e):
/**
 * 取消链接非空节点 x(移除指定节点)
 */
E unlink(Node<E> x) {
    // assert x != null; 假设 x 不等于 null
    final E element = x.item;// 获取节点中的元素
    final Node<E> next = x.next;// 获取下个节点
    final Node<E> prev = x.prev;// 获取上个节点

    // 要移除掉指定的节点,那么需要修改的地方有:
    // 要被移除掉节点的上个节点中的 next 和 下个节点中的 pred
    // 第一个判断是修改前者的
    if (prev == null) {// 如果上个节点是空的(被删除的元素是第一个元素)
        first = next;// 将下个节点赋值给头节点
    } else {// 如果被移除的节点不是第一个元素
        prev.next = next;// 重新指向到下个节点
        x.prev = null;// prev 没用了,置为 null
    }

    // 这个判断是修改后者的
    if (next == null) {// 被移除节点就是最后一个节点
        last = prev;// 将尾节点从被移除节点重新置为上个节点
    } else {// 后面还有节点
        next.prev = prev;// 将后面节点的上个节点重新指向被移除节点的上个节点
        x.next = null;// next 没用了,置为 null
    }

    x.item = null;// 将元素置为 null
    size--;// 长度 -1
    modCount++;// 修改次数 +1
    return element;// 返回被移除的元素
}

remove(Object o)

/**
 * 从列表中删除第一个出现的指定元素(如果该元素存在)
 * 返回 true 表示列表结构已被修改,false 表示没有修改
 */
public boolean remove(Object o) {
    // 就是循环遍历所有元素,然后移除掉相等于 o 的元素(支持 null)
    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;
}

removeLast()

/**
 * 从列表中删除最后一个元素并返回
 */
public E removeLast() {
    final Node<E> l = last;// 获取尾节点
    if (l == null)// 同 removeFirst() 方法一样,依然是判断了是否为空
        throw new NoSuchElementException();
    return unlinkLast(l);// 调用 unlinkLast() 方法
}
  • unlinkLast():
private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;// 获取尾节点元素
    final Node<E> prev = l.prev;// 获取尾节点上个节点
    l.item = null;// 元素置为 null
    l.prev = null; // help GC // 上个节点也置为 null
    last = prev;// 尾节点重新置为原尾节点的上个节点
    if (prev == null)// 如果上个节点是空的,那么就表示被移除的尾节点是最后一个节点了
        first = null;// 头节点置为空
    else// 如果前面还有元素
        prev.next = null;// 前面元素的下个元素置为 null 了
    size--;// 长度 -1
    modCount++;// 修改次数 +1
    return element;// 返回被移除的元素
}

removeFirstOccurrence(Object o)

/**
 * 删除列表中第一个出现的指定元素,删除成功返回 true,失败返回 false
 */
public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

removeLastOccurrence(Object o)

/**
 * 删除列表中最后一个出现的指定元素,删除成功返回 true,失败返回 false
 */
public boolean removeLastOccurrence(Object o) {
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

pop()

/**
 * 从此列表的堆栈中弹出一个元素并返回(也就是删除第一个元素)
 */
public E pop() {
    return removeFirst();
}

poll()

/**
 * 删除集合的头元素(第一个元素),并返回。没有数据返回 null
 */
public E poll() {
    final Node<E> f = first;// 获取头节点
    return (f == null) ? null : unlinkFirst(f);// 头节点如果是空则直接返回空,否则移除头节点
}

pollFirst()

/**
 * 删除集合的头元素(第一个元素),并返回。没有数据返回 null
 */
public E pollFirst() {
    // 实现和 poll() 是一样一样的
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

pollLast()

/**
 * 删除集合的尾元素(最后一个元素),并返回。没有数据返回 null
 */
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}

clear()

/**
 * 从此列表中删除所有元素,此方法执行完毕后,集合将是空的
 */
public void clear() {
    // 遍历所有节点,并且把节点的数据置为 null 来帮助 GC 回收空间
    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;// 头尾节点是必须置为 null 的
    size = 0;// 长度为 0
    modCount++;// 修改次数 +1
}

删除的方法都分析完了,虽然看起来有这么多删除方法,可最后实际上差不多都调用的是 unlink() 和 unlinkLast() 方法的实现。

修改方法

set(int index, E element)

/**
 * 用指定的元素替换指定位置处的元素,并返回替换前的元素
 */
public E set(int index, E element) {
    checkElementIndex(index);// 校验 index
    Node<E> x = node(index);// 查找到指定位置节点
    E oldVal = x.item;// 获取节点元素,已便于返回
    x.item = element;// 替换该元素
    return oldVal;// 返回旧元素
}

查询方法

public E get(int index)
public E element() 
public E getFirst()
public E getLast()
public int indexOf(Object o)
public int lastIndexOf(Object o) 
public E peek()
public E peekFirst()
public E peekLast()

get(int index)

/**
 * 获取指定位置处的元素
 */
public E get(int index) {
    checkElementIndex(index);// 校验 index
    return node(index).item;// 返回指定位置处节点中的元素
}

element()

/**
 * 获取列表中的第一个元素
 */
public E element() {
    return getFirst();
}

getFirst()

/**
 * 获取列表中的第一个元素,没有则抛出异常
 */
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

getLast()

/**
 * 获取列表中的最后一个元素,没有则抛出异常
 */
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

indexOf(Object o)

/**
 * 返回此列表中指定元素第一次出现的索引,如果此列表中不包含指定元素,则返回 -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++;// 索引 +1
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

lastIndexOf(Object o)

/**
 * 返回此列表中指定元素最后一次出现的索引,如果此列表中不包含指定元素,则返回 -1
 */
public int lastIndexOf(Object o) {
    // 同上,只不过反过来,从后往前遍历,索引也从长度到 0
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;// 索引是长度 -1,所以放在前面减去
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

peek()

/**
 * 获取列表的第一个元素,没有则返回 null
 */
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

peekFirst()

/**
 * 同 peek()
 */
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
 }

peekLast()

/**
 * 获取列表的最后一个元素,没有则返回 null
 */
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

其他方法

public Object clone()
public boolean contains(Object o)
public int size()

clone()

/**
 * 返回集合的浅层副本(浅拷贝,元素本身不被克隆)
 */
public Object clone() {
    LinkedList<E> clone = superClone();

    // Put clone into "virgin" state  将克隆处于“处女”状态.... "处女"...!???
    clone.first = clone.last = null;
    clone.size = 0;
    clone.modCount = 0;

    // Initialize clone with our elements
    for (Node<E> x = first; x != null; x = x.next)// 循环现在集合中的元素
        clone.add(x.item);// 然后往克隆出来的集合中加

    return clone;
}

contains(Object o)

/**
 * 返回此列表中是否包含指定的元素,包含返回 true,不包含返回 false
 */
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

size()

/**
 * 返回此列表中的元素数
 */
public int size() {
    return size;
}

toArray()

/**
 * 以从第一个到最后一个的顺序,返回一个包含此列表所有元素的数组(Object 类型)
 */
public Object[] toArray() {
    Object[] result = new Object[size];// 初始化和此列表相同大小的数组
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)// 遍历所有元素
        result[i++] = x.item;// 存储到数组中
    return result;
}

toArray(T[] a)

/**
 * 以从第一个到最后一个的顺序,根据指定类型的数组,返回一个包含此列表所有元素的数组
 * 如果指定的数组和此列表的元素数相同,那么则直接对该数组进行赋值并返回该数组
 * 如果指定数组的长度要小于此列表元素数,那么则会按照指定数组的类型和此列表元素数重新创建一个新的数组
 * 如果指定数组的长度要大于此列表元素数,那么在赋值完成后,会将后一个元素置为 null,以表示赋值到这里就结束了
 */
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    if (a.length > size)
        a[size] = null;

    return a;
}

descendingIterator()

/**
  * 返回一个倒序的迭代器
  */
public Iterator<E> descendingIterator() {
    return new DescendingIterator();
}
  • DescendingIterator:
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();
    }
}

实现的 Iterator,区别就是从后往前查找。

listIterator(int index)

/**
 * 返回一个从指定位置开始迭代的迭代器
 */
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);// 获取指定位置的节点
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();
        if (!hasNext())// 没有数据了还调用 next,抛异常
            throw new NoSuchElementException();
        // 后移一位
        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

    public boolean hasPrevious() {
        return nextIndex > 0;
    }

    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();

        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }

    public int nextIndex() {
        return nextIndex;
    }

    public int previousIndex() {
        return nextIndex - 1;
    }

    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();

        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }

    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }

    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }

    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)// 当修改次数和预期修改次数不符时抛出异常
            throw new ConcurrentModificationException();
    }
}

spliterator()

/**
 * 返回一个 LLSpliterator 类(实现自 Spliterator,jdk8 开始提供的,该迭代器和和 Iterator 相比,一个是顺序遍历,一个是并行遍历。
 * 大家有兴趣可以自行详细了解下该类)
 */
@Override
public Spliterator<E> spliterator() {
    return new LLSpliterator<E>(this, -1, 0);
}
/** A customized variant of Spliterators.IteratorSpliterator */
static final class LLSpliterator<E> implements Spliterator<E> {
    static final int BATCH_UNIT = 1 << 10;  // batch array size increment
    static final int MAX_BATCH = 1 << 25;  // max batch array size;
    final LinkedList<E> list; // null OK unless traversed
    Node<E> current;      // current node; null until initialized
    int est;              // size estimate; -1 until first needed
    int expectedModCount; // initialized when est set
    int batch;            // batch size for splits

    LLSpliterator(LinkedList<E> list, int est, int expectedModCount) {
        this.list = list;
        this.est = est;
        this.expectedModCount = expectedModCount;
    }

    final int getEst() {
        int s; // force initialization
        final LinkedList<E> lst;
        if ((s = est) < 0) {
            if ((lst = list) == null)
                s = est = 0;
            else {
                expectedModCount = lst.modCount;
                current = lst.first;
                s = est = lst.size;
            }
        }
        return s;
    }

    public long estimateSize() { return (long) getEst(); }

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

    public void forEachRemaining(Consumer<? super E> action) {
        Node<E> p; int n;
        if (action == null) throw new NullPointerException();
        if ((n = getEst()) > 0 && (p = current) != null) {
            current = null;
            est = 0;
            do {
                E e = p.item;
                p = p.next;
                action.accept(e);
            } while (p != null && --n > 0);
        }
        if (list.modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

    public boolean tryAdvance(Consumer<? super E> action) {
        Node<E> p;
        if (action == null) throw new NullPointerException();
        if (getEst() > 0 && (p = current) != null) {
            --est;
            E e = p.item;
            current = p.next;
            action.accept(e);
            if (list.modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return true;
        }
        return false;
    }

    public int characteristics() {
        return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
    }
}

总结

通过如上源码我们可以发现,LinkedList 是双向链表的实现,没有最大的规定容量,并且允许元素是 null。 相比于 ArrayList 来说,对头尾的增、删的效率很高,只会影响到前后两个节点。但是因为没有索引直接获取元素,所以查询较慢,需要遍历。并且需要更多的空间来存储前后两个节点的信息。

最后再说一句

刚开始写源码分析相关的文章,写的不好。。。并且写完发现这篇文章实在是太长了。。。以后得文章太长的得拆分拆分。。。请各位看观莫怪。。。

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/Airsaid/article/details/50772367
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞