Java集合类框架源码分析 之 LinkedList源码解析 【4】

上一篇介绍了ArrayList的源码分析【点击看文章】,既然ArrayList都已经做了介绍,那么作为他同胞兄弟的LinkedList,当然必须也配拥有姓名!

Talk is cheap,show me code .

撸起袖子加油干,废话不多说,我们这就开始。

按照之前的惯例,我们还是先来看一下类简介,毕竟这是官方给出的最权威的,带领读者以高屋建瓴,统筹全局的视野来对LinkedList进行一个认识:

/**
 * 实现了List/Deque接口的双向链表,实现了List的所有方法,并且允许包含NULL在内的所有类型的元素
 * Doubly-linked list implementation of the {@code List} and {@code Deque}
 * interfaces.  Implements all optional list operations, and permits all
 * elements (including {@code null}).
 *
 * 所有的操作都是在执行双向链表,链表中的索引会从头或者从尾遍历整个链表,具体用那种遍历方式,取决于哪种更接近于指定的索引
 * <p>All of the operations perform as could be expected for a doubly-linked
 * list.  Operations that index into the list will traverse the list from
 * the beginning or the end, whichever is closer to the specified index.
 *
 * 链表为非线程安全的,和ArrayList一样,如果多个线程同时访问修改双线链表,必须是同步操作。
 * <p><strong>Note that this implementation is not synchronized.</strong>
 * If multiple threads access a linked list concurrently, and at least
 * one of the threads modifies the list structurally, it <i>must</i> be
 * synchronized externally.  (A structural modification is any operation
 * that adds or deletes one or more elements; merely setting the value of
 * an element is not a structural modification.)  This is typically
 * accomplished by synchronizing on some object that naturally
 * encapsulates the list.
 *
 * 为避免非同步安全操作问题,最好在创建链表的时候,使用 List list = Collections.synchronizedList(new LinkedList(...)); 进行包装
 * If no such object exists, the list should be "wrapped" using the
 * {@link Collections#synchronizedList Collections.synchronizedList}
 * method.  This is best done at creation time, to prevent accidental
 * unsynchronized access to the list:<pre>
 *   List list = Collections.synchronizedList(new LinkedList(...));</pre>
 *
 * 迭代器创建完成后,除了通过迭代器之外,不能对list进行修改,否则会抛出ConcurrentModificationException异常
 * <p>The iterators returned by this class's {@code iterator} and
 * {@code listIterator} methods are <i>fail-fast</i>: if the list is
 * structurally modified at any time after the iterator is created, in
 * any way except through the Iterator's own {@code remove} or
 * {@code add} methods, the iterator will throw a {@link
 * ConcurrentModificationException}.  Thus, in the face of concurrent
 * modification, the iterator fails quickly and cleanly, rather than
 * risking arbitrary, non-deterministic behavior at an undetermined
 * time in the future.
 */

我们都知道,LinkedList是基于链表的,ArrayList是基于数组,这是说烂的常识,但是具体LinkedList内部是如何来维护一个链表的呢?有哪些关键的额属性和数据结构呢?可能很多coder就说不清楚了,我认为属性是行为的基石,只有定义好了所需要的属性,那么在方法中才能通过合适的逻辑,算法来达到类设计的初衷。下边,我们就从类的属性,来对LinkedList的内部机制进行一点窥探。

	// 源码中定义的变量可以看到,LinkedList中总共维护3个全局变量
    transient int size = 0; // 链表的长度,初始为0

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
	 * 头元素的指针
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
	 * 尾元素的指针
     */
    transient Node<E> last; 

从属性定义,我们可以看到,LinkedList中主要维护了3个变量,其中最重要的,当属first/last指针,故名思意,这两个指针,一个指向链表头,一个指向链表尾,以便于在遍历链表的时候,根据传入的索引值,和选择合适的方式(从头还是从尾)来对链表进行遍历,提升速度。

属性看完了,我们就来看下对应的方法,前边都是开胃菜,下边的才是正餐,来,上馒头,白面的!!

add()自然又是首当其冲,人家也就能当得起。

和ArrayList类似,add()也是有两个重载方法:

    /**
     * Appends the specified element to the end of this list.
     * <p>This method is equivalent to {@link #addLast}.
     * 追加指定元素到list的末尾,方法等同于 addLast
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
	
    /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * 将指定的元素,插入到list指定的位置。如果指定位置位于list末尾,在直接在最后进行追加,如果不是末尾,那么插入到对应的位置,原来位置及之后的元素,均需要向右移动
     * 即原有的索引位置+1
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
	关注一下其中的node(index)方法:
   /**
     * Returns the (non-null) Node at the specified element index.
	 * 返回指定索引位置的非空节点Node
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);
		
		// 根据索引位置靠前,还是靠后,来使用头指针或者尾指针
		// 这就印证了文章开头类简介中第二部分:【所有的操作都是在执行双向链表,链表中的索引会从头或者从尾遍历整个链表,具体用那种遍历方式,取决于哪种更接近于指定的索引】的描述
        if (index < (size >> 1)) {  // 当指定的索引位置 小于 size/2;即index要插入的位置在前半部分,使用头指针进行遍历
            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;
        }
    }

注意在add(index,e)方法中的node(),就是根据当前所需要插入的索引,是靠近链表头,还是靠近链表尾,来判断具体使用哪个指针来进行对list的遍历操作,再看类属性定义的first/last两个属性,是不是感觉呼应上了?没错。

有存就有取,有add()就有get(),不然就像是存入P2P的血汗钱,被白白的割了韭菜,广大的打工人岂会允许这样的情况发生,我们不做韭菜,LinedList的源码作者说,你说的对,我们也不做,那下边就来看看作者是如何跳过被割的命运:

get()方法闪亮登场:

    /**
     * Returns the element at the specified position in this list.
     *
     * 找到指定位置索引的Node,然后取出其对应的item元素。
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
	

 开始说了吗,emmmmmm,我已经结束了。。。。

额。。。。。

是不是有点快?确实,是有点快,但这丝毫不妨碍get()的重要地位好吧,浓缩的就是精华,再次得到了印证。

忘了那个很快的get()吧,我们再来看看其他的方法,比如:indexOf(o),故名思意,indexOf(o)用来对list链表中的元素进行索引,具体源码如下:

    /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     *
     * 返回元素在list中第一个出现的索引位置,不论该元素是NULL或no-null
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }
	
    /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     *
     * 类似于 indexOf(),只是使用尾指针,从尾部开始遍历
     */
    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

设想这样的场景,我们有了list列表,本想辗转腾挪,有一个大的作为,结果,迎面给你来了一个api,api参数只接收array[]类型,你是不是挺郁闷?

你说,也没什么可郁闷的,我手动来转一下不就行了,这样说没错,自己动手,丰衣足食。但暂且不说自己实现的代码质量如何,总归是需要费一番功夫的,而实际的开发中,时间总是很宝贵的,有那个自己动手写转换的时间,去菜市场买两块生姜,回来擦一下自己日益见顶的头皮,养护一下自己屈指可数的头发,不好么?也算对得起仅剩的那几根,跟你走南闯北,踏波逐浪,见惯风雨,饱尝冷暖,却又依然迎风倔强的头发,他们想尽力维护的,无非就是你哪些和他们数量相当的,所剩无几的体面,而已。

妈的,不写了!!!

好,这就对了。

但是功能依然要实现对吧,拦路虎已经存在的,是吗?该怎么解决呢?jdk真是一个贴心的管家,早就给你想好了,toArray()身披彩霞,脚踏祥云的向你走来了。

你不服,一个toArray()又不是主角,何至于有如此的光彩?

你说的对,但是,对于你眼下的处境,他能帮你一把,那么,此时此景,它就衬得起这份殊荣,芸芸众生,各自挣命,在自己困难的时候,能拉自己一把的人,不多。所以任何能对我这个籍籍无名之辈,落难的时候,伸出一双援手的人,不论这个人也是如何的卑微,他,就是我的神!

来吧,是时候一试真伪了。

toArray()也是一个重载方法,默认情况下,会返回一个新创建的Object[]数组,将传入的列表中的元素,从头到尾的全部依次取出,放入到该数组中,然后返回。

对于toArray(T),就厉害了,可以根据你传入的数组的类型,返回和该参数运行时类型相同的数组。但是两个完全不同的数组。

具体的实现参见如下源码:

    /**
     * 返回一个数组,这个数组使用适当的顺序包含list列表中从头到尾所有的元素;指定的数组也是返回数组的运行时类型。
	 * 如果list符合指定的数组,那么就在其中返回,否则,会按照指定数组的运行时类型以及list的长度,重新分配一个新的数组。
     * Returns an array containing all of the elements in this list in
     * proper sequence (from first to last element); the runtime type of
     * the returned array is that of the specified array.  If the list fits
     * in the specified array, it is returned therein.  Otherwise, a new
     * array is allocated with the runtime type of the specified array and
     * the size of this list.
     *
     *
     * 如果列表符合指定的数组,并且有多余的空间,即:数组的元素数量比list多,那么数组中紧跟在list末尾的元素,
	 * 被设置为null(如果调用者知道list中不包含任何的null元素,这对测定list的长度比较有用)
     * <p>If the list fits in the specified array with room to spare (i.e.,
     * the array has more elements than the list), the element in the array
     * immediately following the end of the list is set to {@code null}.
     * (This is useful in determining the length of the list <i>only</i> if
     * the caller knows that the list does not contain any null elements.)
     *
     * 像 toArray()方法一样,这个方法扮演了基于数组和基于集合的API之间的桥梁。
	 * 进一步来说,这个方法精确控制输出数组的运行时类型,并且也许,在特定的条件下,可以用来节省内存的开销。
     * <p>Like the {@link #toArray()} method, this method acts as bridge between
     * array-based and collection-based APIs.  Further, this method allows
     * precise control over the runtime type of the output array, and may,
     * under certain circumstances, be used to save allocation costs.
     *
     * 假设x是一个只包含字符串类型的list,那么,如下的代码,可以用来将list放入一个新分配的String数组
     * <p>Suppose {@code x} is a list known to contain only strings.
     * The following code can be used to dump the list into a newly
     * allocated array of {@code String}:
     *
     * <pre>
     *     String[] y = x.toArray(new String[0]);</pre>
     *
     * Note that {@code toArray(new Object[0])} is identical in function to
     * {@code toArray()}.
     *
     * @param a the array into which the elements of the list are to
     *          be stored, if it is big enough; otherwise, a new array of the
     *          same runtime type is allocated for this purpose.
     * @return an array containing the elements of the list
     * @throws ArrayStoreException if the runtime type of the specified array
     *         is not a supertype of the runtime type of every element in
     *         this list
     * @throws NullPointerException if the specified array is null
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size); // 根据传入的数组类型,以及所需的长度,重新创建一个数组的实例,两个数组完全不同
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;// 遍历对该新生成的数组进行元素填充

        if (a.length > size)
            a[size] = null;

        return a;
    }
	
    /**
     * 返回一个数组,数组包含list从头到尾的所有的元素
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     * 
     * 返回的数组是安全的,因为list中不包含对该数组的引用
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this list.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * 这个方法扮演了基于数组和基于集合的API之间的桥梁。
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     *
     * 相对于 toArray(T[] a)方法,不需要根据传入的数组的运行时类型进行创建数组,而只是返回Object类型的数组
     * @return an array containing all of the elements in this list
     *         in proper sequence
     */
    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
    }

同样,有新增就有移除,有add()就有remove(),其中相生相克,相爱相杀的含义,已经在上一篇介绍了ArrayList的源码分析【点击看文章】中有了很形象的说明,不再赘述,下边,就来看看remove()方法。

没错,是的,你猜对了,和add()一一对应,remove()自然也是有多个重载方法的:

 public E remove();

public boolean remove(Object o) {}

    /**
     * Retrieves and removes the head (first element) of this list.
     *
     * 删除list中第一个元素
     */
    public E remove() {
        return removeFirst();
    }
	
    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * 如果列表中存在指定的元素,则删除该元素在list中第一次出现的位置的元素,如果list中不包含该元素,该list不发生变化
     * 一般来说,删除该元素在list中最小的索引,从头指针开始遍历。
     * 包含的逻辑为:o==null ? get(i)==null : o.equals(get(i)) ;
     * 
     */
    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;
    }
	看下remove操作中的核心逻辑,即链表中是如何删除一个元素的,它就是上边用到的unlink();
    /**
     * Unlinks non-null node x.
     * 简单说就是掐头去尾,将需要删除的元素的前后关联指针全部斩断,那么,该元素就被剥离除了list,没有了引用的Node,只能等着被GC回收。
     */
    E unlink(Node<E> x) {
        // assert x != null;
        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--;
        modCount++;
        return element;
    }

其实重载方法多或者少,我们都是可以理解的,而且这也是对自然正确的描述,打个比方,打仗亲兄弟,上阵父子兵,为什么呢,兄弟两人去一线打仗,你负责冲锋,我负责断后,兄弟齐心,其利断金,才能攻无不克,战无不胜,才能兄弟双双把家还。

代码也是一样,类似的场景,你不同通过同一个方法全部进行了描述,那这个时候,就是重载的正确时候,你负责处理默认,我负责处理带参的,你负责处理Integer,我负责处理String,是不是感觉清晰了很多,各司其职,高效有序。

我们为这样的设计呐喊,助威,叫床,哦,不对,是叫好。

但是通观LinkedList源码,最让我感觉无语的,也保守广大coder诟病的,是他的方法定义,就像是俄罗斯套娃一样,一个套一个,再套一个,再套一个,方法体中只是简单的调用其他的方法,自己并没有任何的针对性逻辑,这样的设计,就让人很纳闷了?难不成这个类的源码写的过程中,几易其主,然后你写你的,我写我的,大家各自为政?感觉也是属实不可能,所以确实是不理解,为什么是这样的一个设计。

看景不如听景,因为,听景的过程中,讲述者会有很多模糊带过的地方,而这些的模糊的地方,就会让听者发生无限的联想,自动填充很多美好的景色。但是实地一看,原来之前听说的,仅供参考,大失所望。

但是代码不一样,不管写的好或者不好,我们要想理解他,掌握它,改善他,我们就要一行行地扒拉着看,只有这样,我们才能有的放矢,才能心中有数。

废话不多说,欢迎走进,LinkedList之俄罗斯套娃的世界!

    /**
     * 向list所代表的栈中放一个元素,换句话说,就是向list的最前边插入一个元素
     * 和 add()的异同也很明显,add()==linkLast()==addLast(),向最后追加元素.push==addFirst();
     * Pushes an element onto the stack represented by this list.  In other
     * words, inserts the element at the front of this list.
     */
    public void push(E e) {
        addFirst(e);
    }
	
    /**
     * 删除 list 所代表的栈的第一个元素,返回的是删除的元素值
     * Pops an element from the stack represented by this list.  In other
     * words, removes and returns the first element of this list.
     *
     * <p>This method is equivalent to {@link #removeFirst()}.
     *
     * @return the element at the front of this list (which is the top
     *         of the stack represented by this list)
     * @throws NoSuchElementException if this list is empty
     * @since 1.6
     */
    public E pop() {
        return removeFirst();
    }
    /**
     * 删除并返回list中的第一个元素,如果list为空,则抛出NoSuchElementException异常。
     * Removes and returns the first element from this list.
     *
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    /**
     * 类似于 unlink()方法,斩断第一个元素和有序元素之间的联系,第二个元素成为头指针
     * Unlinks non-null first node f.
     */
    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;
    }
	
    /**
     * 和 pop()类似,当list非空的时候,返回第一个元素,当list为空的时候,pop()抛出异常,但poll()返回null。
     * Retrieves and removes the head (first element) of this list.
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
	
    /**
     * 返回第一个元素,但是并不删除
     * Retrieves, but does not remove, the head (first element) of this list.
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
	
    /**
     * 检索并返回第一个元素,但不删除
     * Retrieves, but does not remove, the head (first element) of this list.
     */
    public E element() {
        return getFirst();
    }
	
    /**
     * 获取第一个元素,但是不删除,当list为空的时候,抛出NoSuchElementException异常
     * Returns the first element in this list.
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
	
	public ListIterator listIterator(int arg0){}
	public Object getLast(){}
	
    /**
     * removeFirst () == pop();
     * Removes and returns the first element from this list.
     *
     * @return the first element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
	
    /**
     * 从尾指针开始遍历,删除第一个元素,即删除最后一个元素
     * Removes and returns the last element from this list.
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
	
    /**
     * push()==调用==>addFirst() ==调用==> linkFirst() , =====像不像是俄罗斯套娃
	 * 对于LinkedList中存在多个重复方法的问题,网上一堆人在分析,说各自有什么什么的用途,源码作者看见都得喊一声卧槽
	 * 我的看法是:如果多个方法返回值不同,逻辑相同,我也可以理解,但是逻辑相同,返回值都是void的几个方法,非得说有什么区别,我是真得没有看出来
	 * 有一个可能,就是源码作者所在得项目组,写代码是按行数来统计工作量得==:),或者,写代码的时候喝了假酒了吧====:)^_^
     * Inserts the specified element at the beginning of this list.
     *
     * @param e the element to add
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
    /**
     * Links e as first element.
     */
    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; // 作为f的父节点
        size++;
        modCount++;
    }	
	
	
    /**
     * 向list的最后,追加一个元素。
	 * add()==调用==>linkLast(e);addLast()==调用==>linkLast(e); 
	 * 他来了,他来了,他抱着俄罗斯套娃走来了,只不过这次只是嵌套了两层,俄罗斯小套娃,并且返回类型不同,可以原谅。
     * Appends the specified element to the end of this list.
     */
    public void addLast(E e) {
        linkLast(e);
    }
    /**
     * Links e as last element.
     */
    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++;
    }	
	
    /**
     * 说时迟,那时快,提到曹操,就见曹操一个箭步窜了过来,手搭凉棚,四处观瞧,高声喝问:谁叫俺?!
     * 刚说了俄罗斯的小套娃,小套娃颇不服气,你才小,等着,我来叫大哥,结果,offer(E e)就来了。
     * offer(e)==调用==>add(e)==调用==>linkLast(e);就问你服不服??
     * 还好,我已经块习惯了,而且,我预测,后边还有有层出不穷的小套娃,敌军还有5秒到达战场!!
     * Adds the specified element as the tail (last element) of this list.
     */
    public boolean offer(E e) {
        return add(e);
    }
	
    /**
     * 记得吗,刚有一个套娃长这个样子: push()==调用==>addFirst() ==调用==> linkFirst() ;
     * 现在又来了一个:offerFirst(E e)==调用==>addFirst() ==调用==> linkFirst() ;新瓶装旧酒,简直就是不良商家,过期产品,换了标签就敢重新卖。
     * Inserts the specified element at the front of this list.
     */
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
	
    /**
     * 向list的最后,追加一个元素。
	 * 刚才是不是有个俄罗斯小套娃,add()==调用==>linkLast(e);addLast()==调用==>linkLast(e); 	
	 * 人不服气,不只是叫来了自己的大哥,offer(e)==调用==>add(e)==调用==>linkLast(e);证明了自己上边也是有人的,而且人家大哥还不止一个,就是这个:
	 * offerLast(e)==调用==>addLast()==调用==>linkLast(e); 牛不牛逼??
     * Inserts the specified element at the end of this list.
     */
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }
	
    /**
     * 返回第一个元素,但是不删除,如果list为空,返回null  peekFirst() 类似于 getFirst(),但是list为空时不抛出异常。
     * Retrieves, but does not remove, the first element of this list,
     * or returns {@code null} if this list is empty.
     */
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
	 
    /**
	 * 返回最后一个元素,但是不删除 ,list为空时,返回null
     * Retrieves, but does not remove, the last element of this list,
     * or returns {@code null} if this list is empty.
     */
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }
	
    /**
     * pollFirst () == poll() ;直接调用也好,为什么要写完全一样的逻辑呢?套娃也比你强!!!
     * Retrieves and removes the first element of this list, poll
     * or returns {@code null} if this list is empty.
     */
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
	
    /**
     * pollLast () 类似于 removeLast() ,异同之处在于,removeLast()在list为空,抛出 NoSuchElementException 异常,pollLast()在列表为空时,返回null。
     * Retrieves and removes the last element of this list,
     * or returns {@code null} if this list is empty.
     *
     * @return the last element of this list, or {@code null} if
     *     this list is empty
     * @since 1.6
     */
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }
	
    /**
     * Removes the first occurrence of the specified element in this
     * list (when traversing the list from head to tail).  If the list
     * does not contain the element, it is unchanged.
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if the list contained the specified element
     * @since 1.6
     */
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

总结一下,上边方法块中,主要有这么几个调用链:

element() ==调用==> getFirst();
offer(E e) ==调用==> add(e)==调用==> linkLast(e) ;
offerFirst(E e) ==调用==> addFirst(e)==调用==> linkFirst(e);
offerLast(E e) ==调用==> addLast(E e) ==调用==>linkLast(e);
push(E e)==调用==> addFirst(e)==调用==> linkFirst(e);
pop()==调用==> removeFirst();
removeFirstOccurrence(Object o)==调用==> remove() ==调用==> removeFirst();

刚看大小套娃看的是不是情绪挺波动的?为什么波动,是因为明明可以一个方法解决的事,偏偏要多个方法,而且层层嵌套,看源码时,像剥洋葱一样,一层又一层的打开,最终发现,啊,还是你!

但是在多个方法实现同样逻辑的情况下,最起码套娃有一个优点。什么,套娃还有优点?!

当然,破船还有三斤铁呢不是,谁还没有个优点了!

套娃到底有什么优点呢,优点就是,甭管表面套几层,是如何的故弄玄虚,他们的底层逻辑是相同的,因为调用的是同一个方法嘛

就如同上边的调用链,不管是几层调用,最终都会落脚到唯一的一个实现的方法中,上层的调用都是单纯的调用,而没有任何逻辑,顶多会改变一下返回的类型,将void 改变成 boolean。

这样我们在看的时候,就需要在脑子中维护一个栈,一层层的弹出,最终看最底层的方法实现就行了,这样来看,是不是又感觉轻松了点?

别着急,生活总喜欢在你出其不意,放松警惕的时候,给你来一记重拳,让你认清社会的险恶。

就比如:removeLastOccurrence();先看源码:

    /**
     * 按照从头到尾的遍历顺序,删除list中最后一个出现的元素,如果list不包含该元素,则list不发生变化。
     * removeFirstOccurrence ==调用==> remove();removeLastOccurrence() 偏偏要自己实现,没有调用 removeFirst();不讲武德!
     * Removes the last occurrence of the specified element in this
     * list (when traversing the list from head to tail).  If the list
     * does not contain the element, it is unchanged.
     *
     */
    public boolean removeLastOccurrence(Object o) {
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

源码看着是不是有点眼熟,但是记不清在哪个方法中实现过?没关系,我们用其中一行代码:for (Node<E> x = last; x != null; x = x.prev)  在类源码中搜索一下,结果发现,这不就是 lastIndexOf(o)方法的实现吗?!

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

对对,轻点打,我确实是没瞎,他俩也确实是不一样,但是请睁开你的24K钛金,哦,美姿兰大眼睛好好看看,他俩的底层逻辑是不是基本相同。

然后,我们再来看一下remove(index)方法:

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

有没有发现什么?removeLastOccurrence(o) 是不是等于:remove(lastIndexof(o))?相同的吧?

但是再看看removeLastOccurrence()是如何处理的呢?

复制了一份代码,重写了一遍基本相同的逻辑!

在套娃的时候,我就怀疑,这哥们是不是在故意增加代码行数,以彰显自己的工作量,这下更加印证了这个想法^_^,

当然肯定不是这个原因,对的,我就是这么没有立场,人也都是是这么奇怪,你碰见一个普普通通的人,做了一件奇奇怪怪的事,你可能脱口而出,你这做的是个什么JB?!

但是你碰见一个比你牛逼太多的人,做了一件奇奇怪怪的事,你首先会自己琢磨,这莫不是又是在搞什么棋局,应该是有什么深意吧,只是奈于自己的眼界和认知,不理解罢了。即使,之后事实印证了当初牛人确实是马失前蹄,大部分人也会说,人么,难免出错,肯定当时也有其他的因素干扰。

是的,当你牛逼,当你强大的时候,你的失误,都会有无数的人,自动的站出来为你圆场,所以,你还在等什么呢?

但是,立场还是要有的,不要逆廿年之后,当初一心屠龙的少年,自己却变成了恶龙,被摘掉了遮羞布,褪去了神秘的光环,就这么赤裸裸的站在大众面前,接受舆论的审判,这个时候,大家每当想起你之前的豪言壮语,就会把往你头上拍砖的力量增加一分。

扯远了,拽回来。

意思就是,虽然removeLastOccurrence(o)看着写的是重复的代码,而且有悖于代码重用的思想,但是,我们还是接受吧,可能,真的当初作者这么写,是有着某种考虑,比如,相互不影响?毕竟,代码量也不多,重复一下,也是可以接受的。

链表的优势,就是从头从尾都可以,没有什么限制,想怎么做,就怎么做,我们前边介绍了对头部/尾部元素的增加,删除,弹出而不删除等各种方法,那么如果我们想要对list进行遍历的时候,应该怎么做呢?

用 descendingIterator(),没错,这个可以从头到尾进行遍历整个链表,那现在如果想要从尾部开始遍历呢,该怎么做?

你说怎么会有这种需求?

怎么会没有呢?芸芸众生,众口难调,毕竟有人喜欢前边,那肯定就有人喜欢后边,对吧,个人喜好。

回到正题,该怎么处理呢,其实JDK中已经有了实现,那就是:descendingIterator();

    /**
     * 返回一个基于当前list的逆向迭代器
     */
    public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }
	代码调用:
		LinkedList<String> ll = new LinkedList<>() ;
		ll.add("abc");
		ll.add("abc2");
		

		System.out.println("ll:"+ll);
		
		Iterator<String> iterator = ll.descendingIterator() ;
		while(iterator.hasNext()){
			System.out.println(iterator.next());
		}
	结果:
	ll:[abc, abc2]
	abc2
	abc	

是不是很方便?

至此,快要接近尾声了,还有几个方法需要介绍一下:

containsAll(Collection c) ,循环遍历当前列表,是否全部包含指定的集合,源码如下:

	/**
     * 根据迭代器来对list进行遍历,对迭代器返回的每一个元素进行判断,如果包含,为true,否则,为false;
     * 该方法继承自 AbstractCollection
     * <p>This implementation iterates over the specified collection,
     * checking each element returned by the iterator in turn to see
     * if it's contained in this collection.  If all elements are so
     * contained <tt>true</tt> is returned, otherwise <tt>false</tt>.
     */
    public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }

还有,删除当前集合,在指定集合中不包含的元素,retainAll(Collection c ):

    /**
     * 通过返回的迭代器,来判断当前list中的每个元素,在指定的集合中是否包含,如果不包含,则进行删除
     *
     * <p>This implementation iterates over this collection, checking each
     * element returned by the iterator in turn to see if it's contained
     * in the specified collection.  If it's not so contained, it's removed
     * from this collection with the iterator's <tt>remove</tt> method.
     *
     * 如果当前list通过 iterator()方法返回的 iterator迭代器,没有实现remove()方法,
	 * 并且当前list中包含了一个或多个在指定的集合中不存在的元素时(此时会触发 remove()),会抛出 UnsupportedOperationException 异常。
	 * 为什么说当前list可能不包含remove()方法呢,因为 iterator是依赖于各自list自己的实现。
     * <p>Note that this implementation will throw an
     * <tt>UnsupportedOperationException</tt> if the iterator returned by the
     * <tt>iterator</tt> method does not implement the <tt>remove</tt> method
     * and this collection contains one or more elements not present in the
     * specified collection.
     *
     */
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

ArrayList也考虑到了对于集合列表,按照自定义的需求,删除元素:removeIf(Predicate<? super E> filter):

    /**
     * Removes all of the elements of this collection that satisfy the given
     * predicate.  Errors or runtime exceptions thrown during iteration or by
     * the predicate are relayed to the caller.
     *
     * @implSpec
     * The default implementation traverses all elements of the collection using
     * its {@link #iterator}.  Each matching element is removed using
     * {@link Iterator#remove()}.  If the collection's iterator does not
     * support removal then an {@code UnsupportedOperationException} will be
     * thrown on the first matching element.
     *
     * @param filter a predicate which returns {@code true} for elements to be
     *        removed
     * @return {@code true} if any elements were removed
     * @throws NullPointerException if the specified filter is null
     * @throws UnsupportedOperationException if elements cannot be removed
     *         from this collection.  Implementations may throw this exception if a
     *         matching element cannot be removed or if, in general, removal is not
     *         supported.
     * @since 1.8
     */
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

好了,这次是真的结束了,还有几个方法,可能是很直接的用法,没必要进行源码分析,也有部分是之前已经介绍过,可以翻看一下之前的几篇文章。方法列表如下:

	public Spliterator spliterator(){}
	public boolean addAll(int arg0,Collection arg1){}
	public boolean addAll(Collection arg0){}	
	public void clear(){}
	public boolean contains(Object arg0){}
	public int size(){}	
	public Object set(int arg0,Object arg1){}	
	public Iterator iterator(){}
	public boolean equals(Object arg0){}
	public int hashCode(){}
	public List subList(int arg0,int arg1){}
	public ListIterator listIterator(){}
	public String toString(){}
	public boolean isEmpty(){}	
	public final void wait(long arg0,int arg1) throws InterruptedException{}
	public final native void wait(long arg0) throws InterruptedException{}
	public final void wait() throws InterruptedException{}
	public final native Class getClass(){}
	public final native void notify(){}
	public final native void notifyAll(){}
	public Stream stream(){}	
	public boolean removeAll(Collection arg0){}	
	public Stream parallelStream(){}
	public void forEach(Consumer arg0){}
	public void replaceAll(UnaryOperator arg0){}
	public void sort(Comparator arg0){}
	public Object clone(){}	

这篇文章,写的时候,前后耗时,占用了两天内的零零碎碎的时间,不过终于是写完了,个人总归是有所收获的,看到此处的各位,也希望能给大家带来哪怕是一丁点的感悟和提升,也算是不枉费的这些功夫。

如需转载:请注明来源,不胜感激。

 

其他相关文章:

Java集合类框架源码分析 之 Stack源码解析 【9】

Java集合类框架源码分析 之 Vector源码解析 【8】

Java集合类框架源码分析 之 AttributeList源码解析 【7】

Java集合类框架源码分析 之 RoleList源码解析 【6】

Java集合类框架源码分析 之 CopyOnWriteArrayList源码解析 【5】

Java集合类框架源码分析 之 LinkedList源码解析 【4】

Java集合类框架源码分析 之 ArrayList源码解析 【3】

Java集合类框架源码分析 之 接口中是否可以有方法实现 【2】

Java集合类框架源码分析 之 List 接口源码分析 【1】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值