Java集合源码分析之LinkedList_一点课堂(多岸学院)

分析完了ListQueue以后,终于能够看看LinkedList的实现了。LinkedList弥补了ArrayList增删较慢的问题,但在查找方面又逊色于ArrayList,因此在使用时须要根据场景灵活选择。对于这两个频繁使用的集合类,掌握它们的源码并正确使用,可让咱们的代码更高效。java

LinkedList既实现了List,又实现了Deque,前者使它可以像使用ArrayList同样使用,后者又使它可以承担队列的职责。LinkedList内部结构是一个双向链表,咱们在分析ArrayDeque时提到过这个概念,就是扩充单链表的指针域,增长一个指向前一个元素的指针previousnode

AbstractSequentialList

AbstractSequentialListLinkedList的父级,它继承自AbstractList,而且是一个抽象类,它主要为顺序表的链式实现提供一个骨架:dom

This class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "sequential access" data store (such as a linked list). For random access data (such as an array), AbstractList should be used in preference to this class.ide

意思是它的主要做用是提供一个实现List接口的骨架,来减小咱们实现基于链式存储的实现类时所需的工做量。AbstractSequentialList并无作不少特殊的事情,其中最主要的是提供一个方法的默认实现,并将如下方法抽象,以期有更符合场景的实现:函数

public abstract ListIterator<E> listIterator(int index);

其余一些方法的实现都利用了这个listIterator方法,咱们再也不一一查看了。下面咱们分析LinkedList的实现性能

LinkedList的结构

LinkedList的继承结构以下所示:学习

file

能够看到,LinkedList也实现了Cloneablejava.io.Serializable等方法,借鉴于ArrayList的经验,咱们能够想到它的Clone也是浅克隆,在序列化方法也采用了一样的方式,咱们就再也不赘述了。ui

构造方法与成员变量

数据单元Node

在介绍链表结构时提到过,其数据单元分为数据域和指针域,分别存储数据和指向下一个元素的位置,在java中只要定义一个实体类就能够解决了。this

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 int size = 0;

// 第一个节点
transient Node<E> first;

// 最后一个节点
transient Node<E> last;

构造函数

由于链表没有长度方面的问题,因此也不会涉及到扩容等问题,其构造函数也十分简洁了。

public LinkedList() {
}

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

一个默认的构造函数,什么都没有作,一个是用其余集合初始化,调用了一下addAll方法。addAll方法咱们就再也不分析了,它应该是和添加一个元素的方法是一致的。

重要方法

LinkedList既继承了List,又继承了Deque,那它必然有一堆addremoveaddFirstaddLast等方法。这些方法的含义也相差不大,实现也是相似的,所以LinkedList又提取了新的方法,来简化这些问题。咱们看看这些不对外的方法,以及它们是如何与上述函数对应的。

//将一个元素连接到首位
private void linkFirst(E e) {
    //先将原链表存起来
    final Node<E> f = first;
    //定义一个新节点,其next指向原来的first
    final Node<E> newNode = new Node<>(null, e, f);
    //将first指向新建的节点
    first = newNode;
    //原链表为空表
    if (f == null)
        //把last也指向新建的节点,如今first与last都指向了它
        last = newNode;
    else
        //把原链表挂载在新建节点,也就是如今的first以后
        f.prev = newNode;
    size++;
    modCount++;
}

//与linkFirst相似
void linkLast(E e) {
    //...
}

 //在某个非空节点以前添加元素
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //先把succ节点的前置节点存起来
    final Node<E> pred = succ.prev;
    //新节点插在pred与succ之间
    final Node<E> newNode = new Node<>(pred, e, succ);
    //succ的prev指针移到新节点
    succ.prev = newNode;
    //前置节点为空
    if (pred == null)
        //说明插入到了首位
        first = newNode;
    else
        //把前置节点的next指针也指向新建的节点
        pred.next = newNode;
    size++;
    modCount++;
}

//删除首位的元素,元素必须非空
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;
}

private E unlinkLast(Node<E> l) {
    //...
}

//删除一个指定的节点
E unlink(Node<E> x) {
    //...
}

能够看到,LinkedList提供了一系列方法用来插入和删除,可是却没有再实现一个方法来进行查询,由于对链表的查询是比较慢的,因此它是经过另外的方法来实现的,咱们看一下:

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

//能够说尽力了
Node<E> node(int index) {
    // assert isElementIndex(index);
    
    //size>>1就是取一半的意思
    //折半,将遍历次数减小一半
    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;
    }
}

最后,咱们看下它如何对应那些继承来的方法:

//引用了node方法,须要遍历
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

//也可能须要遍历
public void add(int index, E element) {
    checkPositionIndex(index);

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

//也要遍历
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

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

public E element() {
    return getFirst();
}

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

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

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

public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

//...

总结

LinkedList很是适合大量数据的插入与删除,但其对处于中间位置的元素,不管是增删仍是改查都须要折半遍历,这在数据量大时会十分影响性能。在使用时,尽可能不要涉及查询与在中间插入数据,另外若是要遍历,也最好使用foreach,也就是Iterator提供的方式。


【感谢您能看完,若是可以帮到您,麻烦点个赞~】

更多经验技术欢迎前来共同窗习交流: 一点课堂-为梦想而奋斗的在线学习平台 http://www.yidiankt.com/

![关注公众号,回复“1”免费领取-【java核心知识点】] file

QQ讨论群:616683098

QQ:3184402434

想要深刻学习的同窗们能够加我QQ一块儿学习讨论~还有全套资源分享,经验探讨,等你哦! 在这里插入图片描述

相关文章
相关标签/搜索