java LinkedList 源码浅析

  一、概述

  LinkedList是动态数组的另一种实现,底层以双向循环链表为实现基础,它的优势在于可以快速的删除和添加元素,不需要像ArrayList那样移动大量的元素,但对于查找元素需要逐个遍历链表中的元素,进行匹配。所以LinkedList适用于频繁删除和添加元素,较少查找元素的应用场景。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外, LinkedList 类还为在列表的开头及结尾 getremoveinsert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、 队列双端队列

  此类实现 Deque 接口,为 addpoll 提供先进先出队列操作,以及其他堆栈和双端队列操作。

  所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。

注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:

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

  此类的 iteratorlistIterator 方法返回的迭代器是 快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 removeadd 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。

  ★快速失败(快速报错):Java容器有一种保护机制,能够防止多个进程同时修改一个容器的内容,如果在迭代遍历某个容器的过程中,另一个线程介入其中,并且插入、删除或者修改此内容内的某个对象,那么就会出问题:也许迭代过程已经处理过容器中的该元素了,也许还没处理...Java容器类类库采用快速报错(fail-fase)机制,它会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其他进程修改了此容器,就会报ConcurrentModificationException异常。

二、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中也定义了两个变量分别指向链表中的第一个和最后一个结点

    transient Node<E> first;

    transient Node<E> last;

三、构造函数

提供两种构造函数

    public LinkedList() {
    }

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

  其中addAll(c) 这个方法将指定的Collection添加到链表的结尾,
  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;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {//succ是null,说明是在结尾添加,如果不是则记录index处的结点
            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;
    }
  最终调用的是这个 addAll方法,这个方法是在指定位置 index 插入 Collection,使链表后面的元素右移,第一个addAll中传入的 indexsize,因此就是在结尾添加。

四、添加元素

  添加元素到链表尾端:需要判断最后一个结点是否第一个结点,若是则注意把第一个结点赋值为

    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;
     if (l == null)
       first = newNode;
     else
      l.next = newNode;
      size++;
      modCount++;
    }

  添加到succ元素之前,同样需要判断新增加的结点是否为头结点

    /**
     * Inserts element e before non-null Node succ.
     */
    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++;
    }
此外添加还有,大同小异,利用上述的函数,此处就不一一介绍了。

  

booleanoffer(E e)
          将指定元素添加到此列表的末尾(最后一个元素)。
 booleanofferFirst(E e)
          在此列表的开头插入指定的元素。
 booleanofferLast(E e)
          在此列表末尾插入指定的元素。

五、删除元素

 public E remove(int index) {
   checkElementIndex(index);
   return unlink(node(index));
}

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

 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 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);
    }
  可以看到核心方法为unlink和unlinkLast、unlinkFirst,下面看他们的具体实现

  移除某个结点

    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;
    }
同样的unlinkFirst和unlinkLast也都只是双向链表的移除工作,思路基本类似,就不描述了,需要注意的是移除是要判断是否首节点或尾结点

清空全部结点

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

六、Clone

    @SuppressWarnings("unchecked")
    private LinkedList<E> superClone() {
        try {
            return (LinkedList<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

    /**
     * Returns a shallow copy of this {@code LinkedList}. (The elements
     * themselves are not cloned.)
     *
     * @return a shallow copy of this {@code LinkedList} instance
     */
    public Object clone() {
        LinkedList<E> clone = superClone();

        // Put clone into "virgin" state
        clone.first = clone.last = null;
        clone.size = 0;
        clone.modCount = 0;

        // Initialize clone with our elements
        for (Node<E> x = first; x != null; x = x.next)
            clone.add(x.item);

        return clone;
    }
   调用父类的clone()方法初始化对象链表clone,将clone构造成一个空的双向循环链表,之后将header的下一个节点开始将逐个节点添加到clone中。最后返回克隆的clone对象。 

   LinkedListClone方法只是简单的将原来每个nodeitem放到克隆后的对象中,和 ArrayListclone方法一样,LinkedListClone方法也只是浅复制,如果元素为引用类型,那么修改原 list的值会影响克隆的list的值。有关克隆的知识见链接点击打开链接

七、迭代

listIterator(int index)
          返回此列表中的元素的列表迭代器(按适当顺序),从列表中指定位置开始。

  LinkedList还有一个内部类:ListItr。

 ListItr实现了ListIterator接口,可知它是一个迭代器,通过它可以遍历修改LinkedList。在LinkedList中提供了获取ListItr对象的方法:listIterator(int index)。

 public ListIterator<E> listIterator(int index) {
     return new ListItr(index);
 }
  该方法只是简单的返回了一个ListItr对象
descendingIterator()
          返回以逆向顺序在此双端队列的元素上进行迭代的迭代器。

    private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
    }
从类名和上面的代码可以看出这是一个反向的Iterator,代码很简单,都是调用的ListItr类中的方法。
迭代实例

LinkedList<String> list = new LinkedList<String>();
        list.add("First");
        list.add("Second");
        list.add("Thrid");
        System.out.println(list);
        ListIterator<String> itr = list.listIterator();
        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        try {
            System.out.println(itr.next());// throw Exception
        } catch (Exception e) {
            // TODO: handle exception
        }
        itr = list.listIterator();
        System.out.println(list);
        System.out.println(itr.next());
        itr.add("new node1");
        System.out.println(list);
        itr.add("new node2");
        System.out.println(list);
        System.out.println(itr.next());
        itr.set("modify node");
        System.out.println(list);
        itr.remove();
        System.out.println(list);

输出结果:

[First, Second, Thrid]
First
Second
Thrid
[First, Second, Thrid]
First
[First, new node1, Second, Thrid]
[First, new node1, new node2, Second, Thrid]
Second
[First, new node1, new node2, modify node, Thrid]
[First, new node1, new node2, Thrid]

把LinkedList用作堆栈、队列也只是相应的弹栈入栈操作,根本也是和添加删除一致,就不在详述。


LinkedList和ArrayList的区别

  1. ArrayList继承于 AbstractListLinkedList继承于 AbstractSequentialList

  2. ArrayList基于数组, LinkedList基于双向链表,对于随机访问, ArrayList比较占优势,对于插入删除,LinkedList占优势;

  3. LinkedList没有实现自己的 Iterator,但是有 ListIteratorDescendingIterator

  4. LinkedList需要更多的内存,因为 ArrayList的每个索引的位置是实际的数据,而 LinkedList中的每个节点中存储的是实际的数据和前后节点的位置;

  5. ArrayListLinkedList都是非同步的集合。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值