上一篇介绍了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);elselinkBefore(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 GCfirst = next;if (next == null)last = null;elsenext.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;elsef.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;elsel.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]abc2abc    

是不是很方便?

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

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】

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

  1. 【Java源码分析】LinkedList源码分析

    类的定义如下 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E ...

  2. spring源码分析第一天------源码分析知识储备

    spring源码分析第一天------源码分析知识储备 Spring源码分析怎么学? 1.环境准备: 2.思路    看:是什么? 能干啥    想:为什么?     实践:怎么做?         ...

  3. java上传ddi_Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用...

    前面的博客<Android平台dalvik模式下java Hook框架 ddi 的分析(1)>中,已经分析了dalvik模式下 ddi 框架Hook java方法的原理和流程,这里来学习一 ...

  4. Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585 前面的博客<Android平台dalvik模式下java Ho ...

  5. k8s client-go源码分析 informer源码分析(3)-Reflector源码分析

    k8s client-go源码分析 informer源码分析(3)-Reflector源码分析 1.Reflector概述 Reflector从kube-apiserver中list&watc ...

  6. Java 集合系列(4): LinkedList源码深入解析1

    戳上面的蓝字关注我们哦! 精彩内容 精选java等全套视频教程 精选java电子图书 大数据视频教程精选 java项目练习精选 概要 前面,我们已经学习了ArrayList,并了解了fail-fast ...

  7. Java 集合系列(4): LinkedList源码深入解析2

    戳上面的蓝字关注我们哦! 精彩内容 精选java等全套视频教程 精选java电子图书 大数据视频教程精选 java项目练习精选 第4部分 LinkedList遍历方式 LinkedList遍历方式 L ...

  8. 【数据结构-源码分析】HashMap源码分析(超级详细)

    文章内容 1.HashMap简介 2.类结构 3.属性 4.构造方法 5.方法 5.1.put方法(新增) 5.2.resize方法(扩容) 5.3.get方法(遍历) 5.4.remove方法(删除 ...

  9. 夯实Java基础系列19:一文搞懂Java集合类框架,以及常见面试题

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

最新文章

  1. mysql connector bin_mysql-connector-java-xxx-bin.jar包的使用
  2. Oracle NVL函数的用法
  3. Nodejs如何调用Dll模块
  4. php 如何缓存数据字典,使用PHP脚本如何导出MySQL数据字典
  5. HDU1172猜数字 [模拟]
  6. 视音频编解码学习工程:H.264分析器
  7. 用lua扩展你的Nginx(整理)
  8. 计算机网络——物理层设备
  9. python http get 请求_Python:编写HTTP Server处理GET请求
  10. python3语音控制电脑_python语音控制电脑_uusee全屏
  11. 软件测试的现实和理想
  12. Scrapy中文乱码
  13. maven缺失ojdbc6解决方法(手动安装ojdbc6)
  14. 枫叶股票监控免费软件 股票基金涨幅盈亏监控 上班摸鱼炒股神器
  15. 苹果CMS插件安装使用下载苹果CMS插件集合
  16. c语言mooc gps数据处理的数据_利用智能手机GPS测量地球半径
  17. 陈如波律师:孙宇晨说自己“合法合规”站得住脚吗?
  18. flashpaper java_FlashPaper API 说明
  19. 二十四节气和计算机专业,命理学与二十四节气的关系
  20. 9大增长黑客牛人组织盘点,增长黑客发展奠基人!

热门文章

  1. 问佛 -- 作者 活佛:仓央嘉措
  2. 关于一场“信任危机”
  3. OCP-1Z0-051 第81题 SYSDATE函数
  4. 计算机应用基础员工工资表,项目8 工资表数据分析 《计算机应用基础项目化教程》....
  5. 面试题 03.07. 动物收容所
  6. 格式工厂 wav 比特率_DRmare Audio Converter Mac(音频格式转换工具)
  7. QT 简单的写日志功能
  8. 多线程与多进程(转)
  9. 【翁恺】35-流的概念
  10. 网络安全与python语言_最适合网络安全的5种编程语言