上一篇介绍了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 ? get(i)==null : 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 ? get(i)==null : 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 ? get(i)==null : 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集合类框架源码分析 之 Vector源码解析 【8】
Java集合类框架源码分析 之 AttributeList源码解析 【7】
Java集合类框架源码分析 之 RoleList源码解析 【6】
Java集合类框架源码分析 之 CopyOnWriteArrayList源码解析 【5】
Java集合类框架源码分析 之 LinkedList源码解析 【4】
Java集合类框架源码分析 之 ArrayList源码解析 【3】