Java集合源码剖析——基于JDK1.8中LinkedList的实现原理(下)

简介: Java集合源码剖析——基于JDK1.8中LinkedList的实现原理(下)

3.7 unlinkFirst方法


删除操作与添加操作大同小异,需要把当前节点的前驱节点的后继修改为当前节点的后继,以及当前节点的后继结点的前驱修改为当前节点的前驱。


unlinkFirst方法是在表头进行元素的删除,首先做的是将要删除元素的item值保存到一个临时变量element中,最终返回。同时将要删除元素的后继指针保存到next临时指针中。然后将元素删除(即 f.item=nullf.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 无需保存),之后将 itemprev 置为 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集合源码中,获取元素很多时候都会用到 getgetFirstgetLast 这几个。

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方法。

这三个方法pollpollFirstpollLast就是移除双向链表中的第一个元素、最后一个元素。它们的方法内部实际上就是调用了unlinkFirstunlinkLast方法,上面已经说过了。

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++;
}
相关文章
|
3天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
23 3
|
27天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
62 7
|
19天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
101 13
|
20天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
37 5
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
57 12
|
27天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
43 4
|
29天前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。
|
存储 安全 Java
LinkedList源码解读—Java8版本(上)
LinkedList源码解读—Java8版本(上)
168 0
LinkedList源码解读—Java8版本(上)
|
Java
LinkedList源码解读—Java8版本(下)
LinkedList源码解读—Java8版本(下)
139 0