LlinkedList源码剖析

来源:互联网 发布:淘宝买家怎么贷款啊 编辑:程序博客网 时间:2024/06/05 18:31

LinkedList概述

  • LinkedLlist与ArrayList一样实现List接口,知识ArrayList是List接口的大小可变数组的实现。LinkedList是接口表的实现,基于链表实现的方式是的LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList差。
  • LinkedList实现所有可选的列表操作,并允许所有的元素包括null。
    除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作stack、deque或双端队列。
  • 此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
  • LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
  • LinkedList的实现方式决定了所有跟下标相关的操作都是线性时间,而在首段或者末尾删除元素只需要常数时间。为追求效率LinkedList没有实现同步(synchronized),如果需要多个线程并发访问,可以先采用Collections.synchronizedList()方法对其进行包装。

这里写图片描述

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

从这段代码中我们可以清晰地看出LinkedList继承AbstractSequentialList,实现List、Deque、Cloneable、Serializable。其中AbstractSequentialList提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。Deque一个线性 collection,支持在两端插入和移除元素,定义了双端队列的操作。

源码

属性

在LinkedList中提供了两个基本属性size、first、last

    transient int size = 0;    /**     * Pointer to first node.     * Invariant: (first == null && last == null) ||     *            (first.prev == null && first.item != null)     */    transient Node<E> first;    transient Node<E> last;

LinkedList通过first和last引用分别指向链表的第一个和最后一个元素。注意这里没有所谓的哑元,当链表为空的时候first和last都指向null

Node节点对象

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

构造方法

/**     *  构造一个空列表。     */    public LinkedList() {    }    /**     *  构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。     */    public LinkedList(Collection<? extends E> c) {        this();        addAll(c);    }

this():首先调用LinkedList(),构造一个空列表将Collection中的所有元素添加到列表中。

add

Java1.6的addAll

addAll的源码

 // **将“集合(c)”添加到LinkedList中。实际上,是从双向链表的末尾开始,将“集合(c)”添加到双向链表中**public boolean addAll(Collection<? extends E> c){    return addAll(size, c);}//将指定collection中的所有元素从指定位置开始插入此列表。其中index标识在其中插入指定collection的第一个元素的索引public boolean addAll(int index,Collection<? extends E> c){    //若插入的位置小于0或者大于链表长度,则抛出IndexOutOfBoundsException异常    if(index < 0 || index > size)     throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);     Object[] a = c.toArray();     int numNew = a.length;     //若插入的元素为空,则返回false     if (numNew == 0)         return false;     //modCount:在AbstractList中定义的,表示从结构上修改列表的次数         modCount++;     //获取插入位置的节点,若插入的位置在size头则是头结点,否则获取index位置处的节点     Entry<E> successor = (index == size? header : entry(index))     //插入位置的前一个节点,在插入过程中需要修改该节点的next引用:指向插入的节点元素     Entry<E> prodecessor = successor.previous;     //执行插入动作     for(int i = 0; i < numNew; i++){     //a[i]元素节点,successor下一个节点,predecessor上一个节点     Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);     //将插入位置前一个节点和下一个元素引用指向当前元素     predecessor.next = e;     //修改插入位置的前一个节点,这样的做的目的是插入位置右移以一位     predecessor = e;    }sucessor.prevous = predecessor;}//修改容量大小size += numNew;return true;

在addAll()方法中,涉及了两个方法,一个是entry(int index),该方法为LinkedList的私有方法。该方法为LinkedList的私有方法,主要是用来查找index位置的节点元素。

/**     * 返回指定位置(若存在)的节点元素     */    private Entry<E> entry(int index) {        if (index < 0 || index >= size)            throw new IndexOutOfBoundsException("Index: " + index + ", Size: "                    + size);        //头部节点        Entry<E> e = header;        //判断遍历的方向        if (index < (size >> 1)) {            for (int i = 0; i <= index; i++)                e = e.next;        } else {            for (int i = size; i > index; i--)                e = e.previous;        }        return e;    }

从该方法有两个遍历方向中我们也可以看出LinkedList是双向链表,这也是在构造方法中为什么需要将header的前、后节点均指向自己。

java1.7的add

这里只说一下add(int index,E element)

这里写图片描述

1、 现根据index找到要插入的位置
2、 修改引用,完成插入操作

public void add(int index, E element){    checkPositionIndex(index);    if(index == size)    add(element);    else{    Node<E> succ = node(index);    //修改引用    final Node<E> pred = succ.prev;    final Node<E> newNode = new Node<>(pred,e,succ);    succ.prev = newNode;    if(pred == null)//插入位置为0        first = newNode;    else        pred.next = newNode;    size ++;    }}    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;        }    }

remove

这里写图片描述

1、先找到要删除元素的引用
2、修改相关引用,完成删除操作。
在寻找被删元素引用的时候remove(Object o)调用的是元素的equals方法,而remove(int index)使用的是下标计数,两种方式都是线性时间复杂度。在

E unlink(Node<E> x){    final E element = x.item;    final Node<E> next = x.next;    final Node<E> prec = 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--;    renturn element;}

总结:
1. 在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。
2. LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容
3. 注意源码中的Entry entry(int index)方法。该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index

原创粉丝点击