Java 集合 2:LinkedList 源码分析

LinkedList 源码分析

全篇以 Java8 为基础

Java文档

Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null). All of the operations perform as could be expected for a doubly-linked list. Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index. Note that this implementation is not synchronized.

List 接口和 Deque 接口的双向链表实现。允许包括 null 在内的所有元素放入容器中。对容器的索引操作都从近的那一段开始遍历。LinkedList 不是线程安全的。

​ LinkedList 类的声明如下所示,它实现了 List 和 Deque 接口,属于 Java Collections Framework 。此外,还是实现了 Cloneable 和 Serializable 接口,说明它可克隆、可序列化。Deque 接口是一个双端队列,后续可能会介绍。

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

关键属性

​ LinkedList 类的属性如下所示,其中最重要的是 firstlast 这两个节点,抓住了这两个就能抓住整条链表。

/**
 * 序列化相关
 */
private static final long serialVersionUID = 876323262645176354L;

/**
 * 链表内存储数据的数量
 */
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 有一个内部类 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 有两个构造函数,第一个是无参构造函数,第二个接受一个类型为 Collection 的对象来初始化链表。

/**
 * Constructs an empty list.
 */
public LinkedList() {
}

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param  c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

关键方法

public boolean add(E e)

​ 这里插入采用的是尾插法,将新添加的节点放入到链表的尾部。

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

/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    // 添加新节点,接到链表尾部
    final Node<E> newNode = new Node<>(l, e, null);
    // 尾节点设为插入节点
    last = newNode;
    if (l == null)
    	// 尾节点为空,则链表为空,将新节点设为头节点
        first = newNode;
    else
    	// 原尾节点的后继设为插入节点
        l.next = newNode;
    size++;
    modCount++;
}

public void add(int index, E element)

​ 在指定的位置插入一个元素。如果 index == size ,就相当于插入到末尾,否则插入到 index 对应元素的前一个。

public void add(int index, E element) {
	// index >= 0 && index <= size
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

public boolean addAll(Collection<? extends E> c)

​ 批量添加元素,可以指定添加位置,批量添加的逻辑与添加单个添加的逻辑类似,并无特殊之处。node(int index) 方法获取指定位置上的 Node ,根据给定位置的前后使用不同的指针进行遍历。如果 index < size / 2 则从前往后遍历,反之从后往前遍历。

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
    // index >= 0 && index <= size
    checkPositionIndex(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    Node<E> pred, succ;
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }

    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}

Node<E> node(int index) {
    // assert isElementIndex(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;
    }
}

public void clear()

​ 此方法可以清除容器内的所有元素,将链表中的所有节点都置 null 其实是没有必要的,但是这么做可以有效地帮助分代垃圾回收器进行垃圾回收的工作。

public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    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;
    size = 0;
    modCount++;
}

public boolean contains(Object o)

​ 查看某一个对象是否在容器中,利用了 indexOf(Object o) 方法实现此功能。indexOf(Object o) 方法会在容器中查找对象 o 第一次出现的位置,并且返回该位置,如果不存在对象 o ,则返回 -1

public boolean contains(Object o) {
    return indexOf(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++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
} 

public E get(int index)

​ 使用上面提到的 node(int index) 方法来实现。

public E get(int index) {
    // index >= 0 && index < size;
    checkElementIndex(index);
    return node(index).item;
}

public int lastIndexOf(Object o)

​ 这个方法与 indexOf(Object o) 恰恰相反,返回的是最后出现的与对象 o 相等的元素。

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

public E remove(int index)

​ 根据索引位置删除元素,使用 unlink(Node<E> x) 方法实现。unlink(Node<E> x) 方法分别处理前驱指针和后继指针,方法很常规。

public E remove(int index) {
    // index >= 0 && index < size
    checkElementIndex(index);
    return unlink(node(index));
}
E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

public boolean remove(Object o)

​ 与上述类似。

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

public E set(int index, E element)

​ 使用 node(int index) 找到节点,然后修改值,返回旧值。

public E set(int index, E element) {
    // index >= 0 && index < size
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

public void addFirst(E e)

​ 向队列的头部添加一个元素。

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

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

public void addLast(E e)

​ 向队列的尾部添加一个元素。

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

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

public boolean offer(E e)

​ 等价于向队列尾部添加一个元素

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

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

public E peek()

​ 查看队列的首元素,但不删除它。

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

public E poll()

​ 查看队列的首元素并删除它。

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

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;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

public E pop()

​ 弹出第一个元素。

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

public void push(E e)

​ 添加一个元素,位置在头部。

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

public E remove()

​ 删除第一个元素。

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

总结

​ LinkedList 实现了 List 和 Queue 接口,具有双重功能,可以当列表来使用,也可以当队列来使用。总的代码实现还是很简单的,很容易看明白。和 ArrayList 一样,它的迭代器也是 fast-fail 的,对于并发的修改,它会抛出错误。

发布了14 篇原创文章 · 获赞 0 · 访问量 108

猜你喜欢

转载自blog.csdn.net/aaacccadobe/article/details/103939209