LinkedList源码阅读

文章目录

本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏甚至是谬误,欢迎指出共同学习

本文基于corretto-17.0.9源码,参考本文时请打开相应的源码对照,否则你会不知道我在说什么

简介

从功能上看,LinkedList 同时实现了List 接口和Deque 接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue ),同时又可以看作一个栈(Stack )。这样看来,LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用LinkedList ,一方面是因为Java官方已经声明不建议使用Stack 类,更遗憾的是,Java里根本没有一个叫做Queue 的类(它是个接口名字)。关于栈或队列,现在的首选是ArrayDeque ,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。

从内部实现来看,LinkedList其实只是对链表的封装,没什么特别之处,下面代码分析也会非常简短。与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低,这些显然也是链表和数组的区别。

例子

例子就不看了,都是些一眼懂的API

继承结构

LinkedList,作为一个顺序列表继承于AbstractSequentialList,AbstractSequentialList与AbstractList的区别是,前者一般是不可随机访问的列表,或者说随机访问效率很低,而后者可以高效地基于下标随机访问,LinkedList作为链表的封装,众所周知链表无法随机访问元素,因此继承于AbstractSequentialList。

LinkedList作为List(Sequence),每个元素都对应着一个下标,链表头指针(first)指向的节点下标为0,尾指针(last)指向的节点下标为size-1。

其中JCF相关的接口和抽象类不了解的话,可以在参考「博客园」JCF相关基础类接口/抽象类源码阅读

代码分析

成员变量

LinkedList的成员变量也很少,了解链表的话一眼懂:

java 复制代码
// 元素个数
transient int size = 0;
// 链表头指针
transient Node<E> first;
// 链表尾指针
transient Node<E> last;

// 链表节点类
private static class Node<E> {
  E item;
  LinkedList.Node<E> next;
  LinkedList.Node<E> prev;

  Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
    this.item = element;
    this.next = next;
    this.prev = prev;
  }
}

头和尾指针指向的都是真实元素节点,没有dummy node。注意到Node有next和prev指针,说明是LinkedList维护的是一个双向链表。

方法

首先是构造函数,没啥好说,一个无参构造对应空列表,一个根据Collection接口规范的用其他集合来初始化本集合的构造函数:

java 复制代码
public LinkedList() {}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

下面看一下各个方法,一些链表操作或比较简单的方法就不分析了,只用注释说明功能,先看一下内部方法:

java 复制代码
// 头插新节点
private void linkFirst(E e);
// 尾插新节点
void linkLast(E e);
// 在succ节点前插入新节点
void linkBefore(E e, Node<E> succ);
// 删除头节点
private E unlinkFirst(Node<E> f);
// 删除尾节点
private E unlinkLast(Node<E> l);
// 删除节点
E unlink(Node<E> x);
// 获取下标为index的节点
LinkedList.Node<E> node(int index) {
  if (index < (size >> 1)) { // 如果节点在链表前半部分,则从first开始往后遍历
    LinkedList.Node<E> x = first;
    for (int i = 0; i < index; i++)
      x = x.next;
    return x;
  } else { // 否则从last开始往前遍历
    LinkedList.Node<E> x = last;
    for (int i = size - 1; i > index; i--)
      x = x.prev;
    return x;
  }
}

再看一下公有方法(太简单的公有方法略过)

java 复制代码
// 按c的迭代器的顺序插入链表,最先遍历到的元素下标最小
public boolean addAll(int index, Collection<? extends E> c);
// 清空链表,不像ArrayList的置null,这个方法会删除所有节点
public void clear();

至此,实现Deque的方法已经介绍完。在JCF介绍一文中提到过继承AbstractSequentialList的类要么重写增删改查,要么重新实现列表迭代器,LinkedList两者都做了,增删改查方法很简单,略过,看一下迭代器:

java 复制代码
private class ListItr implements ListIterator<E> {
  // 相当于AbstractList.Itr的lastRet下标对应的节点
  private Node<E> lastReturned;
  // 相当于AbstractList.Itr的cursor下标对应的节点
  private Node<E> next;
  // 相当于Abstrac
  private int nextIndex;
  // 期望的修改次数,用于检测并发修改
  private int expectedModCount = modCount;
}

可以发现LinkedList实现的这个ListIterator没什么特别的,也还是直接对链表进行操作,与AbstractList.ListIterator其实很类似,就不讲了。

java 1.6开始还提供了一个反向迭代器,一眼懂:

java 复制代码
public Iterator<E> descendingIterator() {
  return new DescendingIterator();
}

private class DescendingIterator implements Iterator<E> {
  private final ListItr itr = new ListItr(size());
  public boolean hasNext() {
    return itr.hasPrevious();
  }
  public E next() {
    return itr.previous();
  }
  public void remove() {
    itr.remove();
  }
}

总结

类似ArrayList是对数组的封装,LinkedList是对数组的封装,代码看起来也比较简单,适合编程新手学习一下"封装"的概念。(又水一篇)

参考链接

「博客园」JCF相关基础类接口/抽象类源码阅读

「博客园」ArrayList源码阅读

「Java全栈知识体系」Collection - LinkedList源码解析

相关推荐
木易 士心8 分钟前
Spring AI 核心架构解析:构建企业级 AI 应用的 Java 新范式
java·spring
61900833618 分钟前
linux 安装jdk
java·linux·运维
懂得节能嘛.21 分钟前
【动态配置中心】Java+Redis构建动态配置中心
java·开发语言·redis
专注于大数据技术栈22 分钟前
Java中JDK、JRE、JVM概念
java·开发语言·jvm
YuanlongWang26 分钟前
C# 基础——值类型与引用类型的本质区别
java·jvm·c#
Kay_Liang1 小时前
大语言模型如何精准调用函数—— Function Calling 系统笔记
java·大数据·spring boot·笔记·ai·langchain·tools
自由的疯1 小时前
Java 如何学习Docker
java·后端·架构
自由的疯1 小时前
Java Docker本地部署
java·后端·架构
007php0071 小时前
猿辅导Java面试真实经历与深度总结(二)
java·开发语言·python·计算机网络·面试·职场和发展·golang
摇滚侠1 小时前
Spring Boot 3零基础教程,WEB 开发 内容协商机制 笔记34
java·spring boot·笔记·缓存