007_java.util.LinkedList源码解析

继承体系

image.png
LinkedList的类声明如下:

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

可以看到,LinkedList不仅实现了List接口,还实现了Queue和Deque接口,所以它既能作为List使用,也能作为栈/双端队列使用。LinkedList底层使用节点来表达一个元素,而节点本身将会存储上一个节点及下一个节点的引用,成为“双向链表”。

重要属性

// 元素个数
transient int size = 0;
// 链表首节点
transient Node<E> first;
// 链表尾节点
transient Node<E> last;

可以看到,LinkedList的属性还是相对较少的,ArrayList中承担数据承载的是对象数组与size,LinkedList将对象数据换成了链表的首尾节点。通过这两个节点我们就可以遍历链表上的所有数据。

构造方法

public LinkedList() {}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

LinkedList的构造方法较为简单,一个是默认的无参构造器,不做任何事情,另一个是接收集合,其内部调用addAll构建链表

接口方法

List接口方法

List接口方法在集合的基础上引入索引概念,这里其实现是链表,底层并非使用数组进行存储,因此都需要进行遍历链表找到目标元素。

增加元素

list的增加元素有一个典型方法是针对索引进行增加元素。

// 在指定index位置处添加元素
public void add(int index, E element) {
    // 判断是否越界
    checkPositionIndex(index);
    // 如果index是在队列尾节点之后的一个位置
    // 把新节点直接添加到尾节点之后
    // 否则调用linkBefore()方法在中间添加节点
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

// 寻找index位置的节点
Node<E> node(int index) {
    // 因为是双链表
    // 所以根据index是在前半段还是后半段决定从前遍历还是从后遍历
    // 这样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;
    }
}

// 在节点succ之前添加元素
void linkBefore(E e, Node<E> succ) {
    // succ是待添加节点的后继节点
    // 找到待添加节点的前置节点
    final Node<E> pred = succ.prev;
    // 在其前置节点和后继节点之间创建一个新节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 修改后继节点的前置指针指向新节点
    succ.prev = newNode;
    // 判断前置节点是否为空
    // 如果为空,说明是第一个添加的元素,修改first指针
    // 否则修改前置节点的next为新节点
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    // 修改元素个数
    size++;
    // 修改次数加1
    modCount++;
}

删除元素

remove帮助我们将链表中的指定对象进行删除,由于是链表存储,需要进行遍历操作,得到其节点,然后调用unlink方法将找到的节点对象从链表内删除

public boolean remove(Object o) {
    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;
}

// 删除指定节点x
E unlink(Node<E> x) {
    // x的元素值
    final E element = x.item;
    // x的前置节点
    final Node<E> next = x.next;
    // x的后置节点
    final Node<E> prev = x.prev;
    
    // 如果前置节点为空
    // 说明是首节点,让first指向x的后置节点
    // 否则修改前置节点的next为x的后置节点
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    // 如果后置节点为空
    // 说明是尾节点,让last指向x的前置节点
    // 否则修改后置节点的prev为x的前置节点
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    // 清空x的元素值,协助GC
    x.item = null;
    // 元素个数减1
    size--;
    // 修改次数加1
    modCount++;
    // 返回删除的元素
    return element;
}

Queue接口方法

queue接口有以下定义:

// 队列尾部增加元素
boolean add(E e);
// 移除队列头部的元素并返回
E remove();

// 队列尾部增加元素
boolean offer(E e);
// 移除队列头部的元素并返回
E poll();

// 查看队列尾部元素,不移除
E element();
// 查看队列尾部元素,执行移除
E peek();

其中,add,remove,element如果失败会抛出异常,而offer,poll,peek,失败false或者null。

add/offer方法

public boolean add(E e) {
    linkLast(e);
    return true;
}

void linkLast(E e) {
    // 得到尾部节点
    final Node<E> l = last;
    // 创建节点,并关联尾部节点为前置节点
    final Node<E> newNode = new Node<>(l, e, null);
    // 替换尾部节点为新建节点
    last = newNode;
    // l == null成立是由于空链表首次增加元素,将first赋值新建的接节点
    if (l == null)
        first = newNode;
    else
        // 上一个链表尾节点维护其下一个节点的引用
        l.next = newNode;
    size++;
    modCount++;
}

可以看到add方法内部调用linkLast方法,将新增的元素转换为一个node节点,并连接到链表尾部节点。offer与add的实现逻辑一致

public boolean offer(E e) {
    return add(e);
}

remove/poll方法

public E remove() {
    return removeFirst();
}

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // 获得节点值
    final E element = f.item;
    // 获得节点后续节点
    final Node<E> next = f.next;
    // 置为null
    f.item = null;
    f.next = null; // help GC
    first = next;
    // 如果下一个节点为null则将尾节点置为null
    // 否则将下一个节点的前置节点置为null
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

remove方法帮助将队列头节点剔除,最终调用unlinkFirst方法,其内部将节点的next取出作为first节点,解绑后续节点。poll方法与remove方法一致,只是并不会抛出异常

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

element/peek方法

public E element() {
    return getFirst();
}

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

获取头结点的值,如果头结点为null,则抛出异常。peek与element功能一致,只是并不会抛出异常:

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

Deque接口方法

Deque实现了Queue,由于Deque是双端队列,因此在Quene的基础上增加了对首尾元素的操作能力:

void addFirst(E e);
void addLast(E e);

boolean offerFirst(E e);
boolean offerLast(E e);

E removeFirst();
E removeLast();

E pollFirst();
E pollLast();

E getFirst();
E getLast();

E peekFirst();
E peekLast();

可以看到,对queue中的每个方法都进行了扩充,产生对first/last的操作。由于代码类似,这里选取部分做解释

addFirst/offerFirst

public void addFirst(E e) {
    linkFirst(e);
}

// 从队列首添加元素
private void linkFirst(E e) {
    // 首节点
    final Node<E> f = first;
    // 创建新节点,新节点的next是首节点
    final Node<E> newNode = new Node<>(null, e, f);
    // 让新节点作为新的首节点
    first = newNode;
    // 判断是不是第一个添加的元素
    // 如果是就把last也置为新节点
    // 否则把原首节点的prev指针置为新节点
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    // 元素个数加1
    size++;
    // 修改次数加1,说明这是一个支持fail-fast的集合
    modCount++;
}

removeLast�/pollLast�

removeLast与pollLast功能相似,都是调用unlinkLast进行对尾节点的删除。

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}


// 删除尾节点
private E unlinkLast(Node<E> l) {
    // 尾节点的元素值
    final E element = l.item;
    // 尾节点的前置指针
    final Node<E> prev = l.prev;
    // 清空尾节点的内容,协助GC
    l.item = null;
    l.prev = null; // help GC
    // 让前置节点成为新的尾节点
    last = prev;
    // 如果只有一个元素,删除了把first置为空
    // 否则把前置节点的next置为空
    if (prev == null)
        first = null;
    else
        prev.next = null;
    // 元素个数减1
    size--;
    // 修改次数加1
    modCount++;
    // 返回删除的元素
    return element;
}

栈相关api

除此之外,Deque接口还扩充了对栈的操作。可以看到其语义是使用当前类的addFirst与removeFirst实现。

public void push(E e) {
    addFirst(e);
}
public E pop() {
    return removeFirst();
}

剔除目标节点

Deque中还有一对是新api,表达从链头/未去除第一个相等的节点:

boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);

以removeLastOccurrence举例:

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

判定目标对象是否为null,如果为null则遍历链表判断其节点是否为null,匹配则进行unlink操作。如果对象不为null则使用对象的equals方法进行判定操作

总结

LinkedList的能力定义主要来自三个接口:

  • List
  • Queue
  • Deque

List的引入导致需要针对index进行操作,因此需要针对索引号找到对应的节点。这个能力由node(int index)方法提供。Queue表达队列,因此产生对尾部入队头部出队的操作,该操作主要有两套(add, remove, element) 与 (offer,poll,peek),前者在数据不符合条件的时候会抛出异常,后者不会。Deque在Queue的基础上扩充了上述两套的操作,针对头尾新增xxFirst与xxLast的方法。

LinkedList是一个链表,其内部使用Node维护前置节点与后置节点形成双向链表。因此会产生针对列表的一系列操作:

  • linkFirst:将一个节点链接到链表头部
  • linkLast:将一个节点链接到链表尾部
  • unlink(Node node):将一个节点从链表上去除,这个时候会维护当前节点的前置节点与后置节点之间的关系
  • unlinkFirst:将头结点从链表上去除,这个时候会维护当前节点的前置节点与后置节点之间的关系
  • unlinkLast: 将尾结点从链表上去除,这个时候会维护当前节点的前置节点与后置节点之间的关系
  • linkBefore: 主要服务于索引位置建立节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值