3.7 unlinkFirst方法
删除操作与添加操作大同小异,需要把当前节点的前驱节点的后继修改为当前节点的后继,以及当前节点的后继结点的前驱修改为当前节点的前驱。
unlinkFirst方法是在表头进行元素的删除,首先做的是将要删除元素的item值保存到一个临时变量element中,最终返回。同时将要删除元素的后继指针保存到next临时指针中。然后将元素删除(即 f.item=null,f.next=null,因为删除元素位于表头,所以 f.prev 本身就是 null),删除之后链表中的第二个元素就成为了新的表头,所以修改 first 头指针使其指向之前保存的 next 临时指针(头指针指向后一个结点)。
if判断的是:如果next为空,意思是说所删除元素的后继指针如果为空(又因为该方法是在表头进行元素删除),所以此时链表中仅存的这个结点被删除了,那么整个链表就清空了,所以尾指针 last 就为空了。
else说的是:删除之前链表中存在多个元素,那么当头结点被删除之后,原先头结点之后的那个结点就成了新的头结点,所以这个新的头结点的头指针肯定是null,所以 next.prev=null。
最终返回的是被删除元素的item值。
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; }
3.8 unlinkLast方法
这个方法和上面的差不多,上面的是在表头进行元素的删除,这个是在表尾进行元素的删除。
同样是先保存这个表尾结点的 item 值、prev前驱指针(因为后继指针本身为 null 无需保存),之后将 item、prev 置为 null。当前表尾结点被删除之后,它前面的那个结点就成了新的表尾结点,所以需要将链表的 last 尾指针指向原表尾结点的前驱结点(last=prev)。
if判断的是:如果要删除的表尾结点的前驱为null,则说明此时链表中只有这一个结点,删除之后,链表清空,所以将链表的头指针修改为 null。
else说的是:删除之前链表中有多个元素,将当前表尾结点删除之后,那么它前面的那个结点成了新的表尾结点,所以这个新的表尾结点的后继指针肯定为 null,即 prev.next=null。
最终返回的是被删除元素的item值。
private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; }
3.9 unlink方法
上面两个方法分别是在表头、表尾删除,这个方法则是在链表中的任何一个位置进行元素的删除。
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; }
首先还是先获取要删除元素的item值、next后继指针、prev前驱指针,保存到三个局部变量中。
第一个 if/else:如果前驱指针为null,那么意味着删除的是表头结点,删除之后,新的表头结点为原表头结点的next后继结点,所以将链表的头指针修改指向原表头结点的next后继指针。如果前驱指针不为 null,意味着在链表中间某个位置进行删除操作,需要先修改一下被删除结点的前驱节点的后继指针,使其指向被删除结点的后继指针;之后避免指针冲突,将被删除结点的前驱指针置为null(切断被删除结点的前驱指针这条线)。
第二个 if/else:如果后继指针为null,那么意味着删除的是表尾结点,删除之后,新的表尾结点为原表尾结点的prev前驱结点,所以将链表的尾指针修改指向原表尾结点的prev前驱指针。如果后继指针不为 null,意味着在链表中间某个位置进行删除操作,第一个if/else已经切断了被删除结点的前驱线路,这里还需要修改一下被删除结点的后继节点的前驱指针,使其指向被删除结点的前驱指针;之后避免指针冲突,将被删除结点的后继指针置为null(切断被删除结点的后继指针这条线)。最后将被删除结点的item值也置为null。
最终返回的是被删除元素的item值。
3.10 获取元素的get、getFirst、getLast方法
在LinkedList集合源码中,获取元素很多时候都会用到 get、getFirst、getLast 这几个。
public E get(int index) { checkElementIndex(index); return node(index).item; } public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; } public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }
getFirst方法:获取双向链表中的第一个元素。首先就是拿到当前链表的头指针所指向的头结点,如果为空,则抛出没有这个元素异常;不为空直接就返回表头结点的item值。
getLast方法:获取双向链表中的最后一个元素。首先就是拿到当前链表的尾指针所指向的尾结点,如果为空,则抛出没有这个元素异常;不为空直接就返回表尾结点的item值。
而在get方法的源码中,第一行所做的是进行集合下标是否越界的判断,这里不再多说了。主要是它第二行调用了一个node方法,源码如下:👇👇👇
因为get方法是随机的获取链表中的一个元素,下标为index,那么这个node方法就是帮助get方法完成了对链表的遍历过程。如果获取元素的索引下标小于链表长度/2,则从表头开始遍历,每遍历一次就修改一下当前结点的后继指针,直到遍历到指定元素为止;反之则从表尾开始遍历,每遍历一次就修改一下当前结点的前驱指针,直到遍历到指定元素为止。
Node<E> node(int index) { // assert isElementIndex(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; } }
3.11 size方法
返回双向链表的大小(元素个数)。
public int size() { return size; }
3.12 peek、peekFirst、peekLast方法
这三个方法就是获取双向链表中的第一个元素、最后一个元素。(这源码分析好理解,我就不再多说了)
public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; } public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; }
3.13 pop、poll、pollFirst、pollLast方法
pop方法内部调用了removeFirst方法,而removeFirst方法内部调用了unlinkFirst方法。
这三个方法poll、pollFirst、pollLast就是移除双向链表中的第一个元素、最后一个元素。它们的方法内部实际上就是调用了unlinkFirst、unlinkLast方法,上面已经说过了。
public E pop() { return removeFirst(); } public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); }
3.14 set方法
这个方法非常简单,就是替换指定索引下标位置的元素的item值,同时返回它的旧值。
首先还是进行链表下标是否越界的判断,然后调用node方法对链表进行遍历(查找到要替换的那个元素结点),找到之后将该结点的item值保存一下,然后替换,最后返回该结点的旧值。
public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
3.15 contains方法
判断集合中是否包含某个元素,如果包含在indexOf方法中返回对应的索引下标,在contains方法中返回相应的布尔值。
public boolean contains(Object o) { return indexOf(o) != -1; }
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; }
3.16 clear方法
清空集合中的全部元素。从双向链表的头指针开始遍历,每遍历一次,首先获取当前结点的后继指针指向的结点,然后将当前结点的三要素(前驱、值、后继)置为null,然后更新遍历中心元素为下一个结点next,直至遍历结束。最后链表清空,就将头指针first、尾指针last修改为null,长度size清零。
public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; }