每天读点java源码——LinkedList

读注释

首先咱们来看看源码里的一些注释说明:

Doubly-linked list implementation of the {@code List} and {@code Deque} interfaces. Implements all optional list operations, and permits all elements (including {@code null})

意思就是LinkedList是一个双向链表,实现了List接口和Deque接口。实现了所有的可选的list操作,允许包括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

所有的操作都与双向链表相同,索引链表中的元素时将会从链表头结点或者尾节点开始,这要取决于要索引的元素距离哪边比较近。

ote that this implementation is not synchronized. If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally.

接下来说的是LinkedList并不是同步的,如果多个线程并发的去访问一个链表,并且至少有一个线程对其进行改变,我们必须在外部进行加锁。我们可以使用如下方法来创建安全的链表:

List list = Collections.synchronizedList(new LinkedList(...))

The iterators returned by this class’s {@code iterator} and {@code listIterator} methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the Iterator’s own {@code remove} or {@code add} methods, the iterator will throw a {@link ConcurrentModificationException}.

由列表的iterator和listIterator方法返回的迭代器是快速失败的,通俗点说就是:如果在迭代器生成之后,除了调用迭代器的remove方法或add方法对链表进行修改的操作之外的任何其他方式的修改,都会导致迭代器抛出ConcurrentModificationException异常。也就是说,迭代器生成之后,你可以通过迭代器的remove或add方法去对列表进行修改,不能再调用链表的自身的add或remove方法对链表进行修改。

读源码

首先看看类的头部

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

正如注释里面说的,LinkedList是实现了List和Deque的双向链接,链表的节点使用的是静态内部类:

扫描二维码关注公众号,回复: 11639327 查看本文章
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;
        }
}

可以很明显的看到,确实是一个双向链表,每个节点都有指向前一个和后一个节点的指针。

构造函数

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

有参构造函数可以接受其他类型的集合,如ArrayList

获取首尾元素

// 获取头结点元素
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
}
// 获取尾节点元素
public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
}

这里要注意的是如果链表里没有元素,那么会抛出NoSuchElementException异常

移除首尾节点

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

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

准确的说,这两个方法应该是返回并移除首尾节点,类似的,如果链表为空也会抛出NoSuchElementException异常,我们进去看看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;
}

熟悉链表结构的其实这里一看就明白,首先获取要移除的元素f,暂存为element,然后拿到f的下一个元素,将f的next指针置空,有利于垃圾回收;将链表的头指针指向f.next,如果链表只有一个元素,直接把链表尾结点置空,如果多于一个元素,则把f.next.prev置空,这样就把要移除的元素彻底从链表中断开。最后记录链表长度,修改次数。

在头尾处添加元素

// Inserts the specified element at the beginning of this list.
public void addFirst(E e) {
        linkFirst(e);
}
// Inserts the specified element at the end of this list.
public void addLast(E e) {
        linkLast(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++;
}

我们看看在头部添加元素,首先暂存头结点为f,然后将头结点first指向新添加的节点,如果链表为null,那么尾结点也指向新节点,不为null则将原来的头结点的prev指向新节点。注意这里在构造新节点的时候已经将新节点的next指向了原来的头结点。

链表是否包含某一元素

public boolean contains(Object o) {
        return indexOf(o) >= 0;
}

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

判断链表是否包含某一元素分为两类,若待判断元素为null和不为null,包含就返回对应的索引值,否则返回-1。

添加或移除元素

// Appends the specified element to the End of this list.
public boolean add(E e) {
        linkLast(e);
        return true;
}

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

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

add()方法等同于上面的addLast(),在链表末尾添加元素。移除某一元素与判断链表是否包含某一元素有点类似,也是分为两种情况,待移除元素为null和不为null。如果相同的元素有多个,则移除遇到的第一个。移除操作很简单,无非是将移除的节点前后指针调整一下,释放掉该结点就行了。

模拟进栈出栈 or 进队出队操作

// Inserts the specified element at the front of this list.
public boolean offerFirst(E e) {
        addFirst(e);
        return true;
}
// Inserts the specified element at the end of this list.
public boolean offerLast(E e) {
        addLast(e);
        return true;
}

// Retrieves, but does not remove, the first element of this list
public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
}

// Retrieves, but does not remove, the last element of this list
public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
}

// Retrieves and removes the first element of this list
public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
}

// Retrieves and removes the last element of this list
public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
}

// 或者用一以下两个模拟栈操作,内部使用的方法跟offerFirst一样
public void push(E e) {
        addFirst(e);
}
public E pop() {
        return removeFirst();
}

以上方法的组合使用可以模拟进栈出栈 或 队列的相关操作。

链表转化为数组

public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
}

猜你喜欢

转载自blog.csdn.net/chenshufeng115/article/details/94743454