集合篇:List—LinkedList源码解析

1.整体架构

LinkedList底层的数据结构是一个双向链表,结构如下图,
Image
双向链表结构中每个节点可以向前和向后追溯,该结构的几个特点需要注意,

  • 链表中每个节点是Node类对象
  • first表示双向链表头节点,其前一个节点为null
  • last表示双向链表尾节点,其后一个节点为null
  • 当链表中没有数据时,first和last是指向同一个Node对象,其前后节点都是null
  • 双向链表区别于数组,只要机器的内存足够大,并没有大小的限制

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

该类是LinkedList类的一个内部类,其中三个成员变量的说明如下,

变量类型变量名说明
E(泛型)item节点值
Node<E>next下一个节点对象
Node<E>prev前一个节点对象

LinkedList实现了Queue接口和Deque接口,在添加、删除和查找元素时有不同的方法可以使用。

2.源码解析

查询节点(依据索引)

链表中查询某个节点是比较慢的,需要对链表进行遍历,使用的是LinkedList类node方法

/**
 * Returns the (non-null) Node at the specified element index.
 */
Node<E> node(int index) {
    // 如果 index 处于队列的前半部分,从头开始找; 
    if (index < (size >> 1)) {
        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;
    }
}

LinkedList在查找节点时并没有采用从头循环到尾,而是采取了简单的二分法。使得循环次数减半,提高了查询性能。这种设计方式值得借鉴。

LinkedList查找元素方法调用形式

LinkedList实现的Queue接口提供了两种元素查找方法,用于取出头节点item值。在链表为空时返回值不同,

方法返回值类型空链表情况
elementE抛出异常
peekE返回null

LinkedList实现了List接口的get方法用于获取指定位置节点的item值

方法返回值类型空链表情况
get(int index)E获取指定索引处节点的item值

LinkedList实现额Deque接口支持从头尾两种方向获取元素,

方法返回值类型空链表情况
getFirstE获取头节点的item值,链表为空会抛出异常
getLastE获取尾节点的item值,链表为空会抛出异常
  • element方法
/**
 * 检索并返回链表头节点(不删除)
 * @return the head of this list
 * @throws NoSuchElementException if this list is empty
 */
public E element() {
    return getFirst();
}

/**
 * Returns the first element in this list.
 * @return the first element in this list
 * @throws NoSuchElementException if this list is empty
 */
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}
  • peek方法
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
  • get方法
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
  • 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;
}

添加元素

LinkedList添加元素的操作都是链表操作,有三种操作形式分别在头部、尾部和中间指定位置添加。

linkLast方法尾部添加

完整的过程可以表示为,
Image

/**
 * Links e as last element.
 */
void linkLast(E e) {
	// l是当前的尾节点,newNode是追加节点(未来的尾节点)
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;

	// 如果l是null,表示之前双向链表中没有数据,则追加后,first也会指向newNode
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    
    // 更新大小和版本信息
    size++;
    modCount++;
}

尾部追加节点过程很简单,只需要简单的把指向位置修改即可。

linkFirst方法头部添加

/**
 * Links e as first element.
 */
private void linkFirst(E e) {
	// f是当前链表头节点,newNode是添加到头部后链表新的头节点
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;

	// 如果原本的头节点为null,说明之前链表中没有数据,则尾节点last也要指向newNode
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;

	// 更新大小和版本信息
    size++;
    modCount++;
}

头部追加节点是对原本链表头节点的prev变量的引用进行修改。

linkBefore方法添加到指定节点前

linkBefore方法指定将节点添加到链表指定节点前,

/**
 * 指定添加到某个索引位置上
 */
void linkBefore(E e, Node<E> succ) {
    // 获取指定节点succ的prev对象,并将succ的prev指向newNode
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    
    // 如果pred为null,说明succ是头节点,则first指向新的头节点newNode;反之,pred的next指向newNode
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

LinkedList的添加元素方法调用形式

LinkedList实现了List接口,

调用形式返回值类型说明
add(E e)boolean默认添加到链表尾部,方法调用linkLast方法
add(int index, E element)void添加到链表指定索引位置,方法调用linkBefore方法

LinkedList实现Queue接口和Deque接口后新增的添加元素的方法,

调用形式返回值类型说明
offer(e)boolean添加指定元素到链表尾部
addFirst(e)void添加元素到链表头部,方法调用linkFirst方法
offerFirst(e)boolean添加指定元素到链表头部,方法调用addFirst
addLast(e)void添加元素到链表尾部,方法调用linkLast方法
offerLast(e)boolean添加指定元素到链表尾部,方法调用addLast

删除节点

节点删除与追加类似同样定义了3种删除方法,删除过程是将节点的item、prev和next成员变量都指向null,有助于GC。

unlinkFirst方法头部删除节点

removeFirst方法删除头部节点并返回头节点的item值,

/**
 * Removes and returns the first element from this list.
 * @return the first element from this list
 * @throws NoSuchElementException if this list is empty
 */
public E removeFirst() {
    final Node<E> f = first;
    // 当头部节点为null时,抛出NoSuchElementException 
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

当确定头节点不为null后,调用unlinkFirst方法将头节点从链表中移除并返回头节点的item值,

/**
 * Unlinks non-null first node f.
 */
private E unlinkFirst(Node<E> f) {
    // next指向当前链表第二个节点
    final E element = f.item;
    final Node<E> next = f.next;

	// 头节点的item、next指向null,有助于GC
    f.item = null;
    f.next = null;

	// 第二个节点设定为新的头节点,如果next是null说明移除头节点后链表为空,则last也要指向null
    first = next;
    if (next == null)
        last = null;
    else	// 如果新头节点不是null,设定其prev为null
        next.prev = null;

	// 更新大小和版本信息并返回先前头节点的item值
    size--;
    modCount++;
    return element;
}

unlinkLast方法尾部删除节点

removeLast方法删除尾部节点并返回删除的节点item值,

/**
 * Removes and returns the last element from this list.
 * @return the last element from this list
 * @throws NoSuchElementException if this list is empty
 */
public E removeLast() {
    final Node<E> l = last;
    // 如果尾节点是null,说明链表为空,抛出异常;反之,调用方法返回相应的值
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

当确定头节点不为null后,调用unlinkLast方法将头节点从链表中移除并返回头节点的item值,逻辑与unlinkFirst相同。

/**
 * Unlinks non-null last node l.
 */
private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null;
    last = prev;

	// 如果prev是null,说明链表被删空,此时first需要指向null
    if (prev == null)
        first = null;
    else	// 如果没被删空,则设置prev指向null
        prev.next = null;
    size--;
    modCount++;
    return element;
}

unlink方法删除指定节点

unlink方法传入Node对象,在链表中删除该节点,

/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
    // 获取该节点的值,及其前后节点
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

	// 如果prev为null说明该节点是头节点,则first指向next;反之,next是prev的后一个节点
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

	// 如果next为null,表示该节点是尾节点,则last指向prev;反之prev是next的前一个节点
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

	// 删除的节点item、prev和next设为null,更新大小和版本并返回值
    x.item = null;
    size--;
    modCount++;
    return element;
}

LinkedList对象remove方法调用形式

LinkedList实现了List接口,

调用形式返回值类型说明
remove(Object o)boolean指定值o,删除链表中第一个item等于o的节点。方法调用unlink方法。
remove(int index)E删除指定位置的节点,并返回其item值。方法调用unlink方法。

LinkedList实现Queue接口和Deque接口后新增的添加元素的方法,

调用形式返回值类型说明
poll()E检索并删除头节点,当链表为空时返回null。方法调用unlinkFirst方法。
pollFirst()E检索并删除头节点,当链表为空时返回null。方法调用unlinkFirst方法。
pollLast()E检索并删除尾节点,当链表为空时返回null。方法调用unlinkLast方法。
removeFirst()E检索并删除头节点,当链表为空时抛出异常。方法调用unlinkFirst方法。
removeLast()E检索并删除头节点,当链表为空时抛出异常。方法调用unlinkLast方法。
remove()E检索并删除头节点,当链表为空时抛出异常。方法调用removeFirst。
removeFirstOccurrence(Object o)boolean删除第一次出现的item等于o的节点,方法调用remove(Object o)方法。
removeLastOccurrenceObject o)boolean删除最后一个的item等于o的节点,方法调用unlink方法。

迭代器

LinkedList要是想双向迭代访问,所以Iterator接口不能满足需求,因为Iterator只支持从头到尾的访问。Java新增了一个迭代接口,ListIterator,该接口提供了向前和向后的迭代方法,

迭代方向方法
从头到尾hasNext、next、nextIndex
从尾到头hasPrevious、previous、previousIndex

LinkedList中的内部类ListItr实现了该接口,

private class ListItr implements ListIterator<E> {
	//上一次执行 next() 或者 previos() 方法时的节点位置
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    //expectedModCount:期望版本号;modCount:目前最新版本号
    private int expectedModCount = modCount;

	ListItr(int index) {
		// 创建时依据index得到next的位置
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
	......
}

前向迭代

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

public int nextIndex() {
    return nextIndex;
}

public E next() {
	//检查期望版本号有无发生变化
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();

	// 此处next是当前节点,lastReturned指向该节点。之后对next进行更新,指向下一节点
    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

后向迭代

从尾部到头部的迭代稍微复杂,

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

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

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

	// 如果当前节点是null,表示上次迭代的是尾节点,next和lastReturned指向last即可;反之,指向当前节点的前一节点
    lastReturned = next = (next == null) ? last : next.prev;
    nextIndex--;
    return lastReturned.item;
}

相较于前向迭代的复杂之处在于判断next是否为空。

迭代器删除

调用的是ListIterator的remove方法,

public void remove() {
    checkForComodification();
    // next()和previous()方法中,lastReturned指向的都是迭代出的值。remove方法就是要删除lastReturned指向的值
    // 如果为null,说明之前已经被删除过,不能重复删除,否则会报错
    if (lastReturned == null)
        throw new IllegalStateException();

    Node<E> lastNext = lastReturned.next;
    // 删除该节点
    unlink(lastReturned);
    
    // 之前执行previous方法后,lastReturned和next指向同一个节点对象
    // 此时要删除该对象,则next需指向该节点的后一节点。
    // nextIndex无需改动,因为previous方法中已经对其-1操作
    if (next == lastReturned)
        next = lastNext;
    // 之前执行next方法后,lastReturned和next指向不同对象
    // 此时删除lastReturned不需要对next改动
    // 只用对nextIndex减1即可,因为next方法中对该变量+1
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

总结

LinkedList适用于对顺序有要求并按照顺序迭代的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值