【Java学习003】Java-LinkedList源码解析

LinkedList 源码解析

1.1 底层实现

定义:LinkedList 基于 List 接口的双链链表实现。

结构特点:

  • 增删块(只限中间节点);
  • 查询慢;

1.2 继承关系

1.2.1Serializable 接口

该接口是一个标记型接口,即没有方法或字段,仅用于标识可序列化的语义。不实现此接口的类将不会使任何状态序列化或反序列化。可序列化类的所有子类型都是可序列化的。

[!Note]- 1.8 源码注释

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java™ Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class–serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

1.2.2Cloneable 接口

Cloneable 接口同样也是一个标记型接口,没有任何的字段或者方法。
实现该接口的意义在于指示 Object.clone() 方法,调用方法对于该类的实例进行字段的复制是合法的。在不实现 Cloneable 接口的实例上调用 clone() 方法会抛出**CloneNotSupportedException **异常。

[!Note]- 1.8 源码注释

A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.
Invoking Object’s clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.
By convention, classes that implement this interface should override Object.clone (which is protected) with a public method. See Object.clone() for details on overriding this method.
Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.

补充知识:

  1. 实现克隆的前提:
  • 实现 Cloneable 接口。
  • 重写 clone 方法。(该方法创建的对象与原对象的地址不同)
  1. 浅拷贝的局限性:
  • 基本数据类型可以达到完全复制,引用数据类型则不可以。
  • 引用数据类型进行克隆的时候只是单纯复制了一份引用。

1.2.3List 接口

该接口允许添加元素,可以通过整数索引访问元素,并在列表中搜索元素。该接口允许出现重复的元素(e1.equals(e2) 或者多个 null 值),所有的元素以一种线性方式进行存储,可以通过索引来访问集合中的指定元素。另外,还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

[!Note]- 1.8 源码注释
An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

Unlike sets, lists typically allow duplicate elements. More formally, lists typically allow pairs of elements e1 and e2 such that e1.equals(e2), and they typically allow multiple null elements if they allow null elements at all. It is not inconceivable that someone might wish to implement a list that prohibits duplicates, by throwing runtime exceptions when the user attempts to insert them, but we expect this usage to be rare.

The List interface places additional stipulations, beyond those specified in the Collection interface, on the contracts of the iterator, add, remove, equals, and hashCode methods. Declarations for other inherited methods are also included here for convenience.

The List interface provides four methods for positional (indexed) access to list elements. Lists (like Java arrays) are zero based. Note that these operations may execute in time proportional to the index value for some implementations (the LinkedList class, for example). Thus, iterating over the elements in a list is typically preferable to indexing through it if the caller does not know the implementation.
The List interface provides a special iterator, called a ListIterator, that allows element insertion and replacement, and bidirectional access in addition to the normal operations that the Iterator interface provides. A method is provided to obtain a list iterator that starts at a specified position in the list.
The List interface provides two methods to search for a specified object. From a performance standpoint, these methods should be used with caution. In many implementations they will perform costly linear searches.
The List interface provides two methods to efficiently insert and remove multiple elements at an arbitrary point in the list.

Note: While it is permissible for lists to contain themselves as elements, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a list.

Some list implementations have restrictions on the elements that they may contain. For example, some implementations prohibit null elements, and some have restrictions on the types of their elements. Attempting to add an ineligible element throws an unchecked exception, typically NullPointerException or ClassCastException. Attempting to query the presence of an ineligible element may throw an exception, or it may simply return false; some implementations will exhibit the former behavior and some will exhibit the latter. More generally, attempting an operation on an ineligible element whose completion would not result in the insertion of an ineligible element into the list may throw an exception or it may succeed, at the option of the implementation. Such exceptions are marked as “optional” in the specification for this interface.

1.2.4Deque 接口

Deque 接口代表双端队列的意思,支持两端元素插入和移除的线性集合。大多数 Deque 实现对其可能包含的元素数量没有固定限制,但此接口支持容量受限的双端队列以及没有固定大小限制的双端队列

该接口定义了访问双端队列两端元素的方法。提供了插入、删除和检查元素的方法。这些方法中的每一个都以两种形式存在:一种在操作失败时抛出异常,另一种返回特殊值( null 或 false ,具体取决于操作)。后一种形式的插入操作是专门为容量受限的 Deque 实现而设计的;在大多数实现中,插入操作不会失败。

[!Note]- 1.8 源码注释
A linear collection that supports element insertion and removal at both ends. The name deque is short for “double ended queue” and is usually pronounced “deck”. Most Deque implementations place no fixed limits on the number of elements they may contain, but this interface supports capacity-restricted deques as well as those with no fixed size limit.
This interface defines methods to access the elements at both ends of the deque. Methods are provided to insert, remove, and examine the element. Each of these methods exists in two forms: one throws an exception if the operation fails, the other returns a special value (either null or false, depending on the operation). The latter form of the insert operation is designed specifically for use with capacity-restricted Deque implementations; in most implementations, insert operations cannot fail.

The twelve methods described above are summarized in the following table:

1.3 成员变量

// 链表长度,初始值0
transient int size = 0;

// 永远指向首节点
transient Node<E> first;

// 永远指向末尾节点
transient Node<E> last;

1.4 内部类详解

1.4.1Node

定义:双链链表节点类型,一个 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;
        }
    }

1.5 构造方法

1.5.1 空参构造方法

空参构造方法,创建一个没有任何节点的空 LinkedList 对象。

public LinkedList() {
    
}

1.6.1 有参构造

public LinkedList(Collection<? extends E> c) {  
    // 调用空参构造
    this();  
    // 调用addAll方法添加全部元素
    addAll(c);  
}

public boolean addAll(Collection<? extends E> c) {  
    // 调用底层的添加方法
    return addAll(size, c);  
}

// 真正的添加方法
public boolean addAll(int index, Collection<? extends E> c) {  
    // 检查添加的索引是否合理
    checkPositionIndex(index);  

    // 将集合转换成数组,进行了泛型擦除
    Object[] a = c.toArray();  
    // 获取集合长度
    int numNew = a.length;  
    // 长度为0返回false,fast-fail模式
    if (numNew == 0)  
        return false;  

    // 定义两个指针
    Node<E> pred, succ;  
    // 如果链表末尾添加,则后继指向null,前驱指向last
    if (index == size) {  
        succ = null;  
        pred = last;  
    } else {  // 中间插入的情况
        // 寻找index节点succ和和index节点的前驱节点pred
        succ = node(index);  
        pred = succ.prev;  
    }  
  
    for (Object o : a) {  
        @SuppressWarnings("unchecked") E e = (E) o;  
        // 添加节点    pre <- e -> null
        Node<E> newNode = new Node<>(pred, e, null);  
        // 没有前驱节点,则该节点就是首节点,first指向它
        if (pred == null)  
            first = newNode;  
        else  
            // pre <-> e1 -> null
            pred.next = newNode; 
        // 下一轮就是 e1 <- e2 ->null 
        pred = newNode;  
    }  

    // 如果是末尾添加那么最后一个节点就是尾节点 last指向它
    if (succ == null) {  
        last = pred;  
    } else {  // 如果是插入到链表中间则连接index节点
        // 此时pred指向的是添加集合的最后一个元素
        pred.next = succ;  
        succ.prev = pred;  
    }  

    // 更改链表长度
    size += numNew;  
    // 结构更改计数器+1
    modCount++;  
    return true;  
}

// 检查索引
private void checkPositionIndex(int index) {  
    // 调用底层的检测方法
    if (!isPositionIndex(index))  
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));  
}

private boolean isPositionIndex(int index) {  
    // 索引大于等于0或者小于等于size都是合理的,返回true
    return index >= 0 && index <= size;  
}

Node<E> node(int index) {  
    
    // index < size/2则从头(first指向的节点)开始搜索
    if (index < (size >> 1)) {  
        Node<E> x = first;  
        for (int i = 0; i < index; i++)  
            x = x.next;  
        return x;  
    // 否则从后向前搜索,即从last指向的节点开始搜索
    } else {  
        Node<E> x = last;  
        for (int i = size - 1; i > index; i--)  
            x = x.prev;  
        return x;  
    }  
}

这里说明一下,调用无参构造函数是为了后期的 Java 升级版本中发生修改可以实时与它保持一致,这样做保证了程序的可扩展性,方便了后期的代码维护。

添加元素的时候进行了一层包装,实质上底层调用了一个带有索引位置的添加方法。

1.6 常见方法详解

1.6.1 添加方法

add(E e):在末尾添加元素。

public boolean add(E e) {
        // 调用末尾添加方法
        linkLast(e);
        return true;
    }

void linkLast(E e) {
        // 获取末尾节点
        final Node<E> l = last;
        // 创建一个新的节点 l <- e -> null
        final Node<E> newNode = new Node<>(l, e, null);
        // last指向新的节点
        last = newNode;
    
        // 如果l为空,即原始链表就没有元素,则first也指向新的节点
        if (l == null)
            
            first = newNode;
        else
            // 如果原始链表不为空,则l <-> e -> null
            l.next = newNode;
        // 节点数量+1
        size++;
        // 结构修改计数器+1
        modCount++;
    }

add(int index, E element):按照索引进行添加。

public void add(int index, E element) {
        checkPositionIndex(index);
        // 如果添加的索引是链表末尾则还是按着末尾添加执行
        if (index == size)
            linkLast(element);
        else
            // 添加元素
            linkBefore(element, node(index));
    }

void linkBefore(E e, Node<E> succ) {
        // 获取index节点的前驱节点
        final Node<E> pred = succ.prev;
        // 创建新的节点,直接绑定前后两个节点 index.pred <- e -> index
        final Node<E> newNode = new Node<>(pred, e, succ);
        // index.pred <- e <-> index
        succ.prev = newNode;
        // 如果index节点的前驱节点为null,即在链首添加则将first指向添加的新节点。
        if (pred == null)
            first = newNode;
        else
            // index.pred <-> e <-> index
            pred.next = newNode;
        // 链表节点数量和结构修改计数器+1
        size++;
        modCount++;
    }

1.6.2 获取方法

get(int index):获取索引处元素。

public E get(int index) {  
    // 检查索引的合法性
    checkElementIndex(index);// 返回元素,
    return node(index).item;  
}

第一步就进行了索引的合法性检验,详情见上文,如果索引非法会直接抛出异常,所以可以直接返回 node(index).item,这里返回的是 Node 对象的值不是 Node 对象。

1.6.3 删除方法

remove(int index):删除索引处的节点

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;  

    // 如果前驱节点为空,那么将first指向删除节点的后继节点
    if (prev == null) {  
        first = next;  
    } else {  
        // prev -> next
        prev.next = next;
        // null <- x  
        x.prev = null;  
    }  

    // 如果后继为null,那么将last指向删除节点的前驱节点
    if (next == null) {  
        last = prev;  
    } else {  
        // prev <- next
        next.prev = prev;  
        // next -> null
        x.next = null;  
    }  

    // 值置为null
    x.item = null;  
    // 链表数量-1
    size--;  
    // 结构修改计数器+1
    modCount++;  
    // 返回删除节点的值
    return element;  
}

remove(Object o):按照值删除节点。

public boolean remove(Object o) {  
    // 如果要删除的值为null,则使用==进行对比,删除节点的方法还是unlink
    if (o == null) {  
        for (Node<E> x = first; x != null; x = x.next) {  
            if (x.item == null) {  
                unlink(x);  
                return true;  
            }  
        }  
    } else {  
        // 如果要删除的值不是null,则使用equals方法进行对比,删除节点的方法还是unlink
        for (Node<E> x = first; x != null; x = x.next) {  
            if (o.equals(x.item)) {  
                unlink(x);  
                return true;  
            }  
        }  
    }  
    // 如果没有找到值,则返回false
    return false;  
}

1.6.4 清除方法

public void clear() {
        // 逐个节点置为null(值,前驱,后继)
        // 先获取后继节点,再删除本节点的内容
        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
        first = last = null;
        // 链表数量置0
        size = 0;
        // 结构修改计数器+1 
        modCount++;
    }

1.6.5 设置方法

set(int index, E element):设置节点值。

public E set(int index, E element) {  
    // 检查索引的合法性 
    checkElementIndex(index);  
    //获取索引处的节点
    Node<E> x = node(index);  
    // 获取旧的值
    E oldVal = x.item;  
    // 设置新的值
    x.item = element;  
    // 返回旧的值
    return oldVal;  
}

1.6.6 包含方法

contains(Object o):检查链表中是否包含这个指定的值。

public boolean contains(Object t o) {  
    // 返回值所在的索引位置是否=等于=-1,-1代表不存在
    return indexOf(o) != -1;  
}

public int indexOf(Object o) {  
    int index = 0;  
    // null用==判断
    if (o == null) {  
        for (Node<E> x = first; x != null; x = x.next) {  
            if (x.item == null)  
                return index;  
            index++;  
        }  
    } else {  //非null用equals判断
        for (Node<E> x = first; x != null; x = x.next) {  
            if (o.equals(x.item))  
                return index;  
            index++;  
        }  
    }  
    // 找不到返回-1
    return -1;  
}

1.6.7 查找方法

该方法和 indexOf 方法的查找过程是一致的,只是 indexOf 方法是从前向后找,而 lastIndexOf 是从后向前找。

public int lastIndexOf(Object o) {  
    int index = size;  
    if (o == null) {  
        for (Node<E> x = last; x != null; x = x.prev) {  
            index--;  
            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;  
}

1.7 Deque 接口方法

由于 LinkedList 实现了 Deque 接口,所以也具备了双端队列的性质和方法,可以在队首和队尾添加或者删除元素,具体介绍见下文。

1.7.1 获取方法

getFirst:获取链表第一个元素。

public E getFirst() {  
    // 如果first指向null则抛出异常,否则返回first.item
    final Node<E> f = first;  
    if (f == null)  
        throw new NoSuchElementException();  
    return f.item;  
}

getLast:获取链表最后一个元素。

public E getLast() {  
    // 如果last指向null则抛出异常,否则返回last.item
    final Node<E> l = last;  
    if (l == null)  
        throw new NoSuchElementException();  
    return l.item;  
}

1.7.2 添加方法

addFirst:在链表头添加元素。

// 在链表头添加元素
public void addFirst(E e) {  
    linkFirst(e);  
}

private void linkFirst(E e) {  
    final Node<E> f = first;  
    // null <- e -> f
    final Node<E> newNode = new Node<>(null, e, f);  
    // first指向新节点
    first = newNode; 
    // f为空,即新节点没有后继节点,则last指向新节点 
    if (f == null)  
        last = newNode;  
     // 否则 null <- e <-> f
    else  
        f.prev = newNode;  
    // 链表节点数量和结构修改计数器+1 
    size++;  
    modCount++;  
}

addLast:在链表尾添加元素。

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

void linkLast(E e) {  
    final Node<E> l = last;  
    // l<- e -> null
    final Node<E> newNode = new Node<>(l, e, null); 
    // last指向新的节点  
    last = newNode;  
    // 如果l为空,即新节点没有前驱节点,则first指向新节点。
    if (l == null)  
        first = newNode;  
    // 否则 l<-> e -> null
    else  
        l.next = newNode;  
    // 链表节点数量和结构修改计数器+1 
    size++;  
    modCount++;  
}

1.7.3 删除方法

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指向原本的后继节点
    first = next;  
    // 后继节点为nulll,即如果原本链表就只有一个节点,则last指向null
    if (next == null)  
        last = null;  
    else  
        // null  <- next
        next.prev = null;  
    // 链表节点数量-1,结构修改计数器+1
    size--;  
    modCount++;  
    // 返回旧的值
    return element;  
}

removeLast:删除链表最后一个元素。

public E removeLast() {  
    // 没有最后一个元素会抛出异常
    final Node<E> l = last;  
    if (l == null)  
        throw new NoSuchElementException();  
    return unlinkLast(l);  
}

private E unlinkLast(Node<E> l) {  
    // assert l == last && l != null;  
    final E element = l.item;  
    final Node<E> prev = l.prev;  
    // 将值、前驱、后继都指向null
    l.item = null;  
    l.prev = null; // help GC  
     // last指向原本的前驱节点
    last = prev;  
    // 前驱节点为nulll,即如果原本链表就只有一个节点,则first指向null
    if (prev == null)  
        first = null;  
    else  
        // prev -> null
        prev.next = null;  
    // 链表节点数量-1,结构修改计数器+1
    size--;  
    modCount++;  
    // 返回旧的值
    return element;  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猪猪家的大猪猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值