LinkedList源码分析

        Java集合框架实现两个两种线性表,第一种是数组的实现ArrayList,第二种则是链表的实现LinkedList。作为链表,LinkedList具有增加和删除效率高的特点,但是其也有缺点,就是无法随机访问。

        下面就结合LinkedList常用的方法对该实现的源码进行分析。

1. 概括

        LinkedList实现了List, Deque,Cloneable以及Serializable接口。说明使用LinkedList不仅可以使用基本的链表操作,同时还能使用双端队列以及栈的操作。

        LinkedList的结点实现是其内部定义的私有静态类Node。Node类代码如下:

 1 private static class Node<E> {
 2         E item;
 3         Node<E> next;
 4         Node<E> prev;
 5 
 6         Node(Node<E> prev, E element, Node<E> next) {
 7             this.item = element;
 8             this.next = next;
 9             this.prev = prev;
10         }
11     }

        很显然从这里我们能知道,LinkedList实际上是双链表的实现。

        在LlinkedList类里关键的属性有以下三个:

1     transient int size = 0;
2     transient Node<E> first;
3     transient Node<E> last;

        size表示链表里结点的数量,first指向链表的头结点,last指向链表的尾结点。

2. 构造器

        LinkedList提供了两个构造器,一个是无参的默认构造器,没有执行任何操作,另外一个是参数为Collection子类的构造器,该Collection子类的实际类型必须是LinkedList的泛型的子类,该构造器代码如下:

1 public LinkedList(Collection<? extends E> c) {
2         this();
3         addAll(c);
4     }
5 public boolean addAll(Collection<? extends E> c) {
6         return addAll(size, c);
7     }

        这个构造器调用重载的addAll方法,将集合c的所有元素添加到链表的结尾。实质上是调用另外一个版本的addAll方法。下面具体展开。

        首先检查index是否合法,之后集合c变成Object数组,检查Object数组的大小,如果为0则添加失败。之后找到index位置的链表,从index位置开始插入,最后连接上原本的链表结点。

public boolean addAll(int index, Collection<? extends E> c) {
        //检查下标是否合法
        checkPositionIndex(index);
    //将集合c转换成Object数组
        Object[] a = c.toArray();
        int numNew = a.length;
    //检查Object数组长度,如果为0说明没有需要添加的元素
        if (numNew == 0)
            return false;
    //succ指向下标对应的结点,pred则是succ的前驱
        Node<E> pred, succ;
    //如果下标正好是链表长度,则
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            //调用node方法返回特定下标的结点,查找方法首先根据下标是否超过链表长度一半,超过或者等于链表长度一半
            //则从尾结点last开始,从尾向index查找,如果没有超过链表长度一半,则从头结点first开始,从头向index找
            succ = node(index);
            pred = succ.prev;
        }
    //开始将Obejct数组的元素插入到index之后,首先new一个新的结点,其后继是pred,之后检查pred如果是空,说明此时newNode是头结点(?),如果pred非空
    //则按照如图2.1的方式连接双链表的两个结点,一直循环直到Object数组里的元素都被连接上。
        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;
        }
    //如果succ为空,说明是在链表的结尾插入集合里的所有元素,则last指向pred
        if (succ == null) {
            last = pred;
        } else {
   //否则直接连接之前链表里的元素
            pred.next = succ;
            succ.prev = pred;
        }
    //修改链表的长度
        size += numNew;
    //
        modCount++;
        return true;
    }

 

3. add方法

        add方法有两个重载的版本,一个是只有一个泛型参数的版本,也是最常用的版本,另外一个是往特定位置index插入的版本,下面分析最常用的版本。

        add(E e)方法将元素用尾插法插入到链表的结尾。首先new一个前驱为last的新节点newNode,之后last指向新的尾结点newNode,最后检查之前的尾结点如果为空,则新生成的结点作为头结点,否则将原本的尾结点的后继指向newNode,并给链表的长度加一

 1 public boolean add(E e) {
 2         linkLast(e);
 3         return true;
 4     }
 5 void linkLast(E e) {
 6         final Node<E> l = last;
 7         final Node<E> newNode = new Node<>(l, e, null);
 8         last = newNode;
 9         if (l == null)
10             first = newNode;
11         else
12             l.next = newNode;
13         size++;
14         modCount++;
15     }

 

4. get与contains方法

        许多人在使用LinkedList的时候以为其实现的get方法能随机访问,实际上LinkedList实现的get方法还是要遍历链表,其代码如下:

1 public E get(int index) {
2         checkElementIndex(index);
3         return node(index).item;
4     }

        首先检查下标是否合法,合法的话调用之前分析addAll时出现过的方法node(int index),这个方法返回特定下标的结点,根据下标是否超过链表大小的一半来决定,之前已经分析过了,就不再赘述。故在需要大量随机访问的情况下,还是推荐使用ArrayList实现,当然,如果链表长度不是很长,那也无所谓。

        contains方法当链表里存在参数指定的对象时返回true,这里的存在,指的是在参数对象o非空的情况下,存在至少一个链表结点里的item能使得o.equals(item)为真。contains方法本质上是调用indexOf方法,即返回特定元素被保存在链表结点的下标,indexOf方法如果找不到则返回-1。

5. remove方法

        remove方法删除一个与参数相同的元素,准确的说是在链表里删除下标最小的与参数相同的元素。

        删除的方法是从头向尾遍历链表,找到首次出现的,与参数相同的元素,调用unlink方法,在双链表中断开一个结点(具体过程比较简单就不展开了),之后将被删除的元素存在的结点的item置为null,并返回被删除的元素。

posted @ 2021-06-19 17:34  龙刃已准备出鞘  阅读(36)  评论(0编辑  收藏  举报