LinkedList源码解析

来源:互联网 发布:达内c语言视频 编辑:程序博客网 时间:2024/06/06 07:14

Java版本

Java8

特点

  • 同ArrayList一样,实现了List接口;
  • 通过双向链表实现,在List中插入和删除更快;
  • LinkedList可被当作队列,栈,双端队列操作;
  • 顺序访问高效,随机访问效率较低;
  • 同ArrayList,非线程安全

基本概念

  • 链表
    一种线性表,不按线性的顺序存储数据,在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,在插入的时候可以达到O(1)的复杂度,但是查找一个节点或者访问特定编号的节点(好像是用的是折半查找)则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
  • 单向链表
    每个节点只包含后续节点的引用
  • 双向链表
    一个节点包含两个引用,上一个节点,下一个节点

实现原理

LinkedList的实现方式是这样的:

  • LinkedList他在内存里面是离散的,不是连续。
  • LinkedList里面的每一个元素都有下一个元素的引用。
  • 当你想插入某个元素的时候,比如:已有一个LinkedList里面是 A B C三个元素(也就是,A的引用里面放着B,B的引用里面放着C,C的引用里面是null)现在你要在A和B之间插入D,他的做法是这样的将A的引用指向D,D引用指向B,B的引用依然指向C,C的引用还是null。 所以他插入的是非常的快,就是改一下引用。
  • 当你遍历的时候他会这样做,先将A遍历出来然后找A的引用就是B,再将B遍历出来再找B的引用,将C遍历出来,所以遍历也很快。

LinkedList类定义

public class LinkedList<E>    extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • LinkedList继承AbstractSequentialList,其为List的简化实现,里面实现了使用iterator实现的方法;而ArrayList直接继承AbstractList;
  • 实现了List接口
  • 实现Deque接口,Deque一个线性collection,支持在两端插入和移除元素,定义了双端队列的操作;
  • 实现Cloneable接口,能clone;
  • 实现了Serializable,支持序列化;
  • 相比于ArrayList,这里没有RandomAccess,ArrayList支持随机访问,且效率更高,遍历时使用get比迭代器更快;因此,对于遍历LinkedList使用iterator方法,应该要比for循环get()的方法快;

LinkedList类属性

transient int size = 0;      // 存储的节点个数transient Node<E> first;    // 第一个节点transient Node<E> last;     // 最后一个节点//内部类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;        }    }
  • Node<E> first;
    记录该链表存储中的第一个节点

  • Node<E> last;
    记录该链表存储中的最后一个节点。 这两个属性比较重要,当添加或者移除节点时,都需要重新判断判断第一个节点与最后一个节点的值。

  • transient修饰变量,序列化时不输出。

  • private static class Node<E> {}
    使用static修饰的静态内部类,使用Node(E)来表示链表中节点,item表示当前节点存储元素值,next表示下一个节点,prev表示前一个节点。

LinkedList构造方法

public LinkedList() {}public LinkedList(Collection<? extends E> c) {        this();        addAll(c);}
  • LinkedList()
    构建一个空链表;
  • LinkedList(Collection<? extends E> c)
    构建一个包含Collection元素的列表。其实是通过两步实现,先调用LinkedList()构造方法,再调用addAll()方法,将Collection中元素添加进来。

LinkedList核心方法

添加元素

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++;}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++;}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 void addFirst(E e) {       linkFirst(e);}public void addLast(E e) {       linkLast(e);}    public boolean add(E e) {        linkLast(e);        return true;}    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;            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;}
  • public void addFirst(E e);
    头部添加元素,调用linkFirst()方法,新建一个node,将该node的next指向原来的first节点,同时将该节点赋值给first节点。

  • public void addLast(E e);
    向链表尾部添加节点,调用linkLast()方法。

  • public boolean addAll(int index, Collection<? extends E> c);
    向链表中index位置添加集合元素。在该方法中,创建两个节点,pred与succ来存储临时节点变量。pred用来存储插入位置的前节点,succ用来存储,插入前原先位置的节点。然后使用for循环,向链接中添加节点,每次循环,再创建一个新节点,该节点的prev属性指向pred,如果让pred不为空,则表示当前插入位置不链表头部,则将该pred的next属性指向需要插入的节点;若pred为空,则该节点就直接为该链表的头节点。然后再更新pred的值为当前插入的节点。再来处理,原先插入位置的节点succ,对于ArrayList,此时的操作时调用System.arraycope()方法,将所有后续节点往后移动一位,导致ArrayList的插入节点效率低。对于LinkedList,若succ不为空,只需将succ的prev属性,指向pred,同时将pred的next属性指向succ即可。同时统计链表个数的size自加。该过程就类似于,接自行车链条,从中间断开后,将新的放进去,然后再将前后的扣给扣上。

  • 在这里存在一个问题,该方法是在指定index位置上插入集合,那么如何寻找到指定位置的节点,这里调用的是node(index)方法,代码如下。该方法返回index位置的节点。这里不是一上来就从头开始遍历,先判断index,如果小于链表总长度的一半,就从头开始遍历,否则从尾部开始遍历,时间复杂度降为O(n/2)

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 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 boolean remove(Object o)
    移除链表中首次出现指定的元素。主要是调用unlink(Node<E> x)方法。

  • E unlink(Node<E> x)
    类似于更换自行车链条,通过删除节点,获取到前一个节点prev,下一个节点next,将prev.next指向next,将next的prev指向prev。

其它移除方法:

  • remove():
    获取并移除此列表的头(第一个元素)。
  • remove(int index):
    移除此列表中指定位置处的元素。
  • removeFirst():
    移除并返回此列表的第一个元素。
  • removeLast():
    移除并返回此列表的最后一个元素。
  • removeFirstOccurrence(Object o):
    从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
  • removeLastOccurrence(Object o):
    从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。

查找方法

  • get(int index):
    返回此列表中指定位置处的元素。
  • getFirst():返回此列表的第一个元素。
  • getLast():
    返回此列表的最后一个元素。
  • indexOf(Object o):
    返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
  • lastIndexOf(Object o):
    返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。

其它方法

pop(), push(), offer(), peek()等都类似于get(),remove()方法。

总结

  • LindedList双向链表实现。
  • 批量插入快,只用找到指定位置,再改变前后索引即可。不同于ArrayList需要移动元素。
  • 随机访问慢,需要通过头或者尾节点,一个个遍历下去。
  • 若要进行大量的随机访问,使用ArrayList;如果要经常从表中插入或删除元素,应使用LinkedList。

对于ArrayList可以参考博客,ArrayList源码分析

说明

个人对LinkedList一点总结,存在错误地方,欢迎指正。 整理过程中,参考了多人博客,但链接已经找不到,在此没有列出。若有不当之处,联系后将会进行更改。

原创粉丝点击