❤️用武侠小说的形式来阅读LinkedList的源码,绝了!(2)

简介: ❤️用武侠小说的形式来阅读LinkedList的源码,绝了!

addFirst 内部其实调用的是 linkFirst:


public void addFirst(E e) {
    linkFirst(e);
}

linkFirst 负责把新的节点设为 first,并将新的 first 的 next 更新为之前的 first。


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;
    size++;
    modCount++;
}



addLast 的内核其实和 addFirst 差不多,就交给大家自行理解了。


2)招式二:删


我这个删的招式还挺多的:


remove():删除第一个节点

remove(int):删除指定位置的节点

remove(Object):删除指定元素的节点

removeFirst():删除第一个节点

removeLast():删除最后一个节点

remove 内部调用的是 removeFirst,所以这两个招式的功效一样。


remove(int) 内部其实调用的是 unlink 方法。


public E remove(int index) {

   checkElementIndex(index);

   return unlink(node(index));

}



unlink 方法其实很好理解,就是更新当前节点的 next 和 prev,然后把当前节点上的元素设为 null。


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;
}



remove(Object) 内部也调用了 unlink 方法,只不过在此之前要先找到元素所在的节点:


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;
}



这内部就分为两种,一种是元素为 null 的时候,必须使用 == 来判断;一种是元素为非 null 的时候,要使用 equals 来判断。equals 是不能用来判 null 的,会抛出 NPE 错误。


removeFirst 内部调用的是 unlinkFirst 方法:


public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}



unlinkFirst 负责的就是把第一个节点毁尸灭迹,并且捎带把后一个节点的 prev 设为 null。


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)招式三:改


可以调用 set() 方法来更新元素:


list.set(0, "沉默王五");

1

来看一下 set() 方法:


public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}


首先对指定的下标进行检查,看是否越界;然后根据下标查找原有的节点:


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;
    }
}



size >> 1:也就是右移一位,相当于除以 2。对于计算机来说,移位比除法运算效率更高,因为数据在计算机内部都是二进制存储的。


换句话说,node 方法会对下标进行一个初步判断,如果靠近前半截,就从下标 0 开始遍历;如果靠近后半截,就从末尾开始遍历。


找到指定下标的节点就简单了,直接把原有节点的元素替换成新的节点就 OK 了,prev 和 next 都不用改动。


4)招式四:查


我这个查的招式可以分为两种:


indexOf(Object):查找某个元素所在的位置

get(int):查找某个位置上的元素

indexOf 的内部分为两种,一种是元素为 null 的时候,必须使用 == 来判断;一种是元素为非 null 的时候,要使用 equals 来判断。因为 equals 是不能用来判 null 的,会抛出 NPE 错误。


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;
}



get 方法的内核其实还是 node 方法,这个之前已经说明过了,这里略过。


public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}



其实,查这个招式还可以演化为其他的一些,比如说:


getFirst() 方法用于获取第一个元素;

getLast() 方法用于获取最后一个元素;

poll() 和 pollFirst() 方法用于删除并返回第一个元素(两个方法尽管名字不同,但方法体是完全相同的);

pollLast() 方法用于删除并返回最后一个元素;

peekFirst() 方法用于返回但不删除第一个元素。

四、LinkedList 的挑战


说句实在话,我不是很喜欢和师兄 ArrayList 拿来比较,因为我们各自修炼的内功不同,没有孰高孰低。


虽然师兄经常喊我一声师弟,但我们之间其实挺和谐的。但我知道,在外人眼里,同门师兄弟,总要一较高下的。


比如说,我们俩在增删改查时候的时间复杂度。


也许这就是命运吧,从我进入师门的那天起,这种争论就一直没有停息过。


无论外人怎么看待我们,在我眼里,师兄永远都是一哥,我敬重他,他也愿意保护我。


好了,LinkedList 这篇就到这了。


如果大家有闲情逸致的话,建议手撕一下链表,可以从单向链表开始撕起。


希望大家能点赞下,给我注入一点点更新的动力。我也会不断地提升品质,给大家带来更硬核的技术文章,笔芯~


相关文章
|
存储 消息中间件 大数据
大数据-70 Kafka 高级特性 物理存储 日志存储 日志清理: 日志删除与日志压缩
大数据-70 Kafka 高级特性 物理存储 日志存储 日志清理: 日志删除与日志压缩
185 1
网页课程设计-期末大作业-简单设计【原神狂喜】
本文介绍了一个以“原神”为主题的网页课程设计项目,包括登录页、博客首页、文件上传页面、相册页面和留言板页面的设计与实现,并提供了完整的源代码下载链接。
网页课程设计-期末大作业-简单设计【原神狂喜】
|
IDE Java Maven
【Java异常】Error:(14,35) java:程序包eu.bitwalker.useragentutils不存在 的解决方案
【Java异常】Error:(14,35) java:程序包eu.bitwalker.useragentutils不存在 的解决方案
796 0
|
机器学习/深度学习 编解码 PyTorch
基于MeshCNN和PyTorch的三维对象分类和分割
基于MeshCNN和PyTorch的三维对象分类和分割
591 0
基于MeshCNN和PyTorch的三维对象分类和分割
|
算法 Python
使用Python和Gurobi求解无约束优化问题
使用Python和Gurobi求解无约束优化问题
436 0
|
并行计算 数据可视化 算法
CMplot & rMVP | 全基因组曼哈顿图和QQ图轻松可视化!
`CMplot`和`rMVP`是R语言中的两个包,用于全基因组关联分析(GWAS)的数据可视化。`CMplot`专注于曼哈顿图和QQ图的绘制,支持多种图表类型,如常见的SNP密度图、环状曼哈顿图、矩阵图、单条染色体图和多重曼哈顿图等。`rMVP`不仅包含了`CMplot`的功能,还支持更复杂的GWAS方法,如线性/混合线性模型和基因组选择算法,优化了内存管理和计算效率,特别适合大规模数据集。此外,它还提供PCA图和柱状图。两者都提供了丰富的参数定制图表。
1381 1
CMplot & rMVP | 全基因组曼哈顿图和QQ图轻松可视化!
|
存储 安全 Cloud Native
阿里云支持米哈游新游《绝区零》全球开服!
阿里云支持米哈游新游《绝区零》全球开服!
2057 3
|
SQL 网络协议 网络性能优化
网络七层协议详解
网络七层协议详解
1157 1
|
数据可视化 搜索推荐
【数据可视化】预制菜行业分析(一)——国内发展情况
近年来,预制菜开始从大型连锁餐饮企业的中央厨房渗透到外卖餐饮平台,并逐渐从 B 端走向 C 端。消费者购买后只需要简单加工即可食用,省去了食材采购、处理步骤,具有便捷、高效、口味保持度高的特点。
|
分布式计算 运维 搜索推荐
基于阿里云Maxcompute搭建商业广告数据分析系统
互联网时代,信息流广告越来越多。而信息流广告的投放以大数据测算为依托,同样的数据,不同的解读方式,在进行投放指导时会产生不同的效果。
534 0
基于阿里云Maxcompute搭建商业广告数据分析系统