LinkedList源码分析

最近在面试中碰到了许多问题,发现自己的java基础还是需要好好巩固。
今天就让我们来看一下LinkedList.
LinkedList
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

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

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

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

List list = Collections.synchronizedList(new LinkedList(…));此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。

注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
上面的介绍摘自Java JDK注释。
LinkedList的底层数据结构为双向链表。(以下所有源码版本均为JDK1.7)
双向链表,每一个结点都有两个指针,一个指向他的前一个结点,一个指向他之后的一个结点

 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有两个构造函数。
LinkedList()
构造一个空列表。
LinkedList(Collection

       public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    } 

我们来具体分析一下LinkedList有参构造函数,即分析一下java中如何构造一个双向链表

    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    //在索引为index的结点前插入一个collection元素列表
     public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);
//将指定 collection 中的元素的列表转化为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
//给出两个结点变量,一个代表原index位置上的结点,一个代表index前面的一个结点
        Node<E> pred, succ;
      //如果该collection列表是直接放在原LinkedList之后的
        if (index == size) {
        //则有原index位置上的结点为null,index前面的结点为原链表的尾结点
            succ = null;
            pred = last;
        } else {
  //如果是在原链表中间加入列表,则取出原index位置上的结点和其前面的结点
            succ = node(index);
            pred = succ.prev;
        }
        //将列表插入原链表中
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //e.prev=pred;e.next=null;  *(1)

            Node<E> newNode = new Node<>(pred, e, null);
            //如果index为0时,即为第一个结点first时,即此时需要在头结点前插入一个结点
            if (pred == null)
                first = newNode;
            else
            //如果index不为0,pred.next=newNode:与*(1),将新结点e与前一个结点联系完好联系起来,而此时e的后指针指向null;
                pred.next = newNode;
            //将新节点赋给pred;尾插法
            pred = newNode;
        }
    //如果是直接在原链表后插入列表c,则在使用尾插法后只需要重新把最后的一个结点赋给last即可。
        if (succ == null) {
            last = pred;
        } else {
        //如果是在链表中部插入,则此时需要将插入的最后一个元素和原index位置上的元素链接起来
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
        private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
        transient int size = 0;

LinkedList中还有另一种创建链表的方式,也是我们常用的方式
List list=new LinkedList();
list.add();
创建链表使用的尾插法

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

在看一下add(int index, E element)
在此列表中指定的位置插入指定的元素。
看一下移除元素的操作,也就是双向链表的删除操作

    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    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 boolean remove(Object o) {
  //由于LinkedList允许null出现,所以移除时需要分两种情况判断
        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;
                }
            }
        }
          E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        //给出结点x的前后结点
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //如果结点x为首结点,则首结点需要变为x的next结点;
        if (prev == null) {
            first = next;
        } else {

            prev.next = next; 
            x.prev = null;
        }
//如果是尾结点,则为节点需要变为x的prev结点
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

最后,我们看一下LinkedList是怎么实现关键的Iterator接口的,iterator方法的具体实现到底是什么。
首先,我们来看一下List接口中到底有哪些关于迭代器的方法
ListIterator listIterator()
ListIterator listIterator(int index)
Iterator iterator()
对于Iterator接口,我们是比较熟悉的。他有一下几个方法
boolean hasNext()
如果仍有元素可以迭代,则返回 true。
E next()
返回迭代的下一个元素。
void remove()
从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
而ListIterator接口时继承Iterator接口的。他是列表迭代器,仅出现现实List接口的集合。。允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置 始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置。
void add(E e)
将指定的元素插入列表(可选操作)。
boolean hasNext()
以正向遍历列表时,如果列表迭代器有多个元素,则返回 true(换句话说,如果 next 返回一个元素而不是抛出异常,则返回 true)。
boolean hasPrevious()
如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
E next()
返回列表中的下一个元素。
int nextIndex()
返回对 next 的后续调用所返回元素的索引。
E previous()
返回列表中的前一个元素。
int previousIndex()
返回对 previous 的后续调用所返回元素的索引。
void remove()
从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。
void set(E e)
用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。


观看LinkedList源码,我们可以发现,该类并没有实现List接口的iterator方法
只有
public ListIterator listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
Q1:为什么LinkedList实现了List接口却没有实现List接口的方法呢?
这时我们再回头看看LinkedList继承的类和现实的接口
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable

我们再进入AbstractSequentialList中观察,该类继承了AbstractList。
我们看AbstractList发现
public Iterator iterator() {
return new Itr();
}
public ListIterator listIterator() {
return listIterator(0);

}
    public ListIterator<E> listIterator(final int index) {
    rangeCheckForAdd(index);

    return new ListItr(index);
}

他现实了所有和迭代器的相关方法,因为他是直接实现List接口的。使用必须直接实现所有List接口中的集合

再看AbstractSequentialList 他重写了AbstractList的iterator方法
public Iterator iterator() {
return listIterator();
}
public abstract ListIterator listIterator(int index);
AbstractSequentialList 也实现了List接口,但没有显式的实现该接口的所有方法,因为AbstractSequentialList 的父类AbstractList实现了List接口
所以 AbstractSequentialList当然也实现了List接口,但是没有显示的实现接口中的方法
在这里我们就可以回答Q1的问题了,只要该类的父类或者基类实现了某个接口,则这个类自然就实现了这个接口,如果这个类没有显式的显示这个接口中的某些方法
,当将该类的对象赋给该接口的引用时,当调用那些方法时,会调用该类的父类或基类已显式实现的方法。
接下来,我们通过Debug来看看 list.iterator();到底是怎样调用的,最后究竟调用了哪个实现类
图1
图1

图2
图2

图3
图3

图4
图4

图5

图5
第一次调试的时候,我一直对图3到图4这个过程有点纳闷
一直想
public ListIterator listIterator() {
return listIterator(0);

}
这个方法调用的就是自己类内部的方法
     public ListIterator<E> listIterator(final int index) {
    rangeCheckForAdd(index);

    return new ListItr(index);
}

为什么会跳到LinkedList类中去呢
可后来转念一想凭什么是调用你自己内部的方法
Iterator it=list.iterator();
list是接口List的引用
指向的是对象是LinkedList实例。
都跟AbstractList没有直接的关系
呀,原来自己把动态绑定给忘啦!
这里之所以会调用LinkedList中的方法,是因为java的动态绑定的原理,方法的指针最终会指向我们真正new出来的对象实例中的方法。
但是如果LinkedList中没有实现这个方法,那还是会调用AbstractList内部的方法,但是如果实现了,就会根据动态绑定指向真正的实例对象中的方法
接下来看一下

 private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned = null;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        public boolean hasNext() {
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

        public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
LinkedListJava中提供的一个双向链表实现类,其内部维护了一个first和last节点,分别表示链表的头和尾。以下是LinkedList源码分析: 1. 声明LinkedList类 ```java public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { transient int size = 0; transient Node<E> first; transient Node<E> last; } ``` 2. 声明Node类 ```java 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; } } ``` 3. 实现LinkedList方法 - add(E e)方法:将元素添加到链表末尾 ```java 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++; } ``` - add(int index, E element)方法:将元素插入到指定位置 ```java public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } void linkBefore(E e, Node<E> succ) { 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++; } ``` - remove(int index)方法:删除指定位置的元素 ```java public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } E unlink(Node<E> x) { 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--; return element; } ``` - get(int index)方法:获取指定位置的元素 ```java public E get(int index) { checkElementIndex(index); return node(index).item; } Node<E> node(int 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; } } ``` 以上就是LinkedList源码分析,通过对其源码的分析,我们可以更深入地理解链表的实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值