Java源码篇之容器类——LinkedList

一、前言

对于经常在开发中使用到的LinkedList,一直以来只知道底层是链表实现的,但是很好奇具体实现,以此为目的简单阅读一下它的源码,做个记录,jdk1.8版本。

二、LinkedList的类关系

在这里插入图片描述

通过查看LinkedList的类关系图,可以看到实现了

Cloneble接口,支持被克隆;

Serializable接口,支持序列化与反序列化;

Deque接口,支持两端元素插入和移除的线性集合。 名称deque是“双端队列”的缩写。

三、 LinkedList的源码

1、类的属性

/**
 * 链表元素个数
 */
transient int size = 0;

/**
 * 指向第一个节点的指针
 */
transient Node<E> first;

/**
 * 指向最后一个节点的指针
 */
transient Node<E> last;

2、add()方法

// 尾插法添加元素
public boolean add(E e) {
    linkLast(e);
    return true;
}

// 尾插法
void linkLast(E e) {
    // 把链表中最后一个元素(未添加元素时)赋值给l
    final Node<E> l = last;
    // 创建新的节点Node,并把元素e赋值给Node,前驱指向链表中最后一个元素(未添加元素时)①
    final Node<E> newNode = new Node<>(l, e, null);
    // 每次新创建的节点都是最后一个节点,所以此处是尾插
    last = newNode;
    if (l == null)
        // 添加第一个元素的时候,first和last均指向此元素
        first = newNode;
    else
        // 将链表中最后一个元素(未添加元素时)的后继指向新添加的节点②
        // 结合①②可以看出LinkedList是双向链表实现的
        l.next = newNode;
    // 元素个数加1
    size++;
    // 链表修改次数加1
    modCount++;
}

// 指定位置添加元素
public void add(int index, E element) {
    // 校验合法性
    checkPositionIndex(index);

    if (index == size)
        // 如果指定的索引等于链表元素个数
        // 例:链表中有一个元素,索引是从0开始的,0表示第一个位置的元素,1表示第二个位置的元素,所以此处调用尾插添加元素
        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;
}

// 在非空元素succ(目标索引位置上的元素)之前插入元素e
void linkBefore(E e, Node<E> succ) {
    // succ前一个节点
    final Node<E> pred = succ.prev;
    // 在pred和succ之间添加元素e
    final Node<E> newNode = new Node<>(pred, e, succ);
    // succ的前驱指向新创建的元素
    succ.prev = newNode;
    if (pred == null)
        // 说明此索引之前无元素,first指向新创建的节点
        first = newNode;
    else
        // succ前一节点的后继指向新创建的节点,形成双链
        pred.next = newNode;
    size++;
    modCount++;
}

// 内部静态类
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;
    }
}

3、get()方法

public E get(int index) {
	// 校验索引合法性 
    checkElementIndex(index);
    // 返回索引节点的元素值
    return node(index).item;
}

4、remove()方法

// 根据索引删除
public E remove(int index) {
    // 校验索引合法性
    checkElementIndex(index);
    return unlink(node(index));
}

// 此方法才是真正删除节点的方法
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        // x节点的前驱为null,此时x是链表中第一个元素节点
        // 头指针指向下一个节点就表示删除此节点
        first = next;
    } else {
        // x的前驱不为null,x元素的前一节点的后继指向x的后一个元素节点
        prev.next = next;
        // x元素的前驱置为null
        // x元素节点的后继还存在,此时还未完全删除成功
        x.prev = null;
    }

    // 处理该节点的后继指针
    // 指针指向绕过x,表示x元素节点从链表中删除
    if (next == null) {
        // x元素节点的后继为null,表示此为链中最后一个元素节点
        // 尾结点指向此节点的前驱
        last = prev;
    } else {
        // x元素节点的后继不为null
        // 修改该节点的后一个节点的前驱指向,此时已经没有指针指向x元素节点
        next.prev = prev;
        // x元素节点的后继指针置空,此时该节点完全孤立,下一次GC发生时会回收此节点
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    // 返回此节点元素值
    return element;
}

// 根据对象删除
public boolean remove(Object o) {
    // 要删除的对象分为null和非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;
}

5、set()方法

// 将索引位置的元素替换成目前值element
public E set(int index, E element) {
    // 校验索引合法性
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    // 替换目标值
    x.item = element;
    // 返回被替换的值
    return oldVal;
}

四、总结

  • 通过阅读源码可以看出,LinkedList是双向链表实现的;
  • LinkedList是线程非安全的;
  • add()和remove()删除方法只需要修改指针即可,增删类比数组很高效;
原创文章 16 获赞 17 访问量 8万+

猜你喜欢

转载自blog.csdn.net/SGdan_qi/article/details/106070489