LinkedList ArrayDeque源码阅读

news2024/12/26 12:06:28

文章目录

  • LinkedList简介
  • LinkedList例子
  • LinkedList继承结构
  • LinkedList代码分析
    • 成员变量
    • 方法
  • ArrayDeque简介
  • ArrayDeque继承结构
  • ArrayDeque代码分析
  • 总结
  • 参考链接

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

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

LinkedList简介

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

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

LinkedList例子

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

LinkedList继承结构

image-20240110151419222

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

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

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

LinkedList代码分析

成员变量

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

// 元素个数
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接口规范的用其他集合来初始化本集合的构造函数:

public LinkedList() {}

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

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

// 头插新节点
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;
  }
}

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

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

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

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开始还提供了一个反向迭代器,一眼懂:

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

ArrayDeque简介

ArrayDeque是双端队列接口Deque的实现。出于性能上的考虑,如果你要用FIFO队列,可以优先考虑ArrayDeque而不是LinkedList;如果要使用栈,可以优先考虑ArrayDeque而不是LinkedList。ArrayDeque除了个别方法外,基本摊还时间复杂度都是O(1)。这个类的迭代器也是fail-fast的(这个概念在ArrayList那篇中讲过)。

ArrayDeque继承结构

image-20240112155715312

根据继承结构可以确定,ArrayDeque就是一个用数组实现的双端队列,并且由于是“双端”,我们可以进一步推测出应该是用循环数组来实现的。

ArrayDeque代码分析

按照惯例,先搞懂成员变量的含义:

public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable {
  // 实际存储元素的数组,没有存元素的话为null
  transient Object[] elements;
  // 循环数组的头指针,指向头元素
  transient int head;
  // 循环数组的尾指针,指向下一个要入队的尾元素
  transient int tail;
}

注意没有存元素的位置是null,因此ArrayDeque不能存值为null的元素,至于为什么,看完下面分析就知道了。

先复习一下循环数组,当调用addLast的时候,往尾部增加元素;当调用addFirst的时候往头部增加元素,画一张图帮助理解,其中数字代表元素下标,蓝色的代表该位置有元素,其他的不用多解释了:

image-20240112162317556

要注意的是,tail永远要指向null,意味着数组大小为n的话,最多只能存n-1个元素。否则如果存满数组的话,最终tail与head相等,此时无法判断是队空还是队满。

ArrayDeque没有设size变量保存元素个数,因为可以用head和tail计算得到。但是循环数组中tail可能大于head也可能小于head,因此设计一个概念:循环距离。首先tail入队时是递增的,head入队下标是递减的,因此head和tail的循环距离就是(结合图来理解):

  • 当tail>=head,为tail-head
  • 当tail<head,为tail-head+数组长度(相当于数组长度减去空白格子的个数,得到蓝色格子个数)

因此size就等于循环距离。ok,关于循环数组的原理以及元素出入队的细节都搞清楚了,那么由于循环数组是个数组(废话),总会有容量不够的时候,下一步就来分析数组的扩容。扩容的核心方法是grow:

// 最少要增加needed(ArrayList中的grow是最少增加到minCapacity)
private void grow(int needed) {
  final int oldCapacity = elements.length;
  int newCapacity;
  // 这一段是计算扩容后的长度,总体策略是:
  // 如果数组比较小(小于64)那么扩容到2倍,否则扩容到1.5倍
  int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
  if (jump < needed
      || (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
    newCapacity = newCapacity(needed, jump);
  // 这一段是扩容
  final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
  if (tail < head || (tail == head && es[head] != null)) {
    int newSpace = newCapacity - oldCapacity;
    System.arraycopy(es, head,
                     es, head + newSpace,
                     oldCapacity - head);
    for (int i = head, to = (head += newSpace); i < to; i++)
      es[i] = null;
  }
}

grow的最后一段代码是为了处理tail<=head的情况,具体还是看图,假设从之前那张图中数组的最后一个状态开始扩容,大小+3(实际上不一定需要扩容,扩容的话也不一定是+3,这里只是为了方便说明这段代码的最后一个部分):

image-20240112174305731

由于tail < head的情况下,元素是物理不连续的(循环数组嘛),因此最后一段 if 将后面那段移动到新数组的尾部,保持循环数组中元素的连续性。

Deque和Collection的一些方法就不说了,都很简单。挑一点看着相对需要分析的函数来看一下:

// 删除从头到尾的第i个元素
boolean delete(int i) {
  final Object[] es = elements;
  final int capacity = es.length;
  final int h, t;
  // 计算第i个元素与头的循环距离
  final int front = sub(i, h = head, capacity);
  // 计算第i个元素与尾的循环距离
  final int back = sub(t = tail, i, capacity) - 1;
  // 哪个循环距离小,就移动哪部分。这里意思是,因为是循环数组,删除一个元素之后,可以把前半部分的元素整体往后移动,也可以把后半部分的元素整体往前移动,因此选择需要移动元素少的那部分。
  if (front < back) {
    if (h <= i) {
      System.arraycopy(es, h, es, h + 1, front);
    } else { // Wrap around
      System.arraycopy(es, 0, es, 1, i);
      es[0] = es[capacity - 1];
      System.arraycopy(es, h, es, h + 1, front - (i + 1));
    }
    es[h] = null;
    head = inc(h, capacity);
    return false;
  } else {
    tail = dec(t, capacity);
    if (i <= tail) {
      System.arraycopy(es, i + 1, es, i, back);
    } else {
      System.arraycopy(es, i + 1, es, i, capacity - (i + 1));
      es[capacity - 1] = es[0];
      System.arraycopy(es, 1, es, 0, t - 1);
    }
    es[tail] = null;
    return true;
  }
}

还有的函数比如bulkRemoveModified,其实跟ArrayList的removeIf很像了,也是先标记所有将要被删除的元素,然后再进行逐个删除,这种做法是为了filter进行逐元素进行判断时,保持遍历过程中列表是可重入读的,在实际进行删除时使用双指针法保持元素连续性。具体可以参考「博客园」ArrayList源码阅读。

最后看一眼迭代器吧,因为JCF前几篇都已经详细介绍过迭代器,ArrayDeque的迭代器实现也没什么特别的,就不展开讲了:

private class DeqIterator implements Iterator<E> {
  int cursor;
  int remaining = size();
  int lastRet = -1;
}

总结

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

java 1.6新出了一个ArrayDeque,出于性能上的考虑,如果你要用FIFO队列,可以优先考虑ArrayDeque而不是LinkedList;如果要使用栈,可以优先考虑ArrayDeque而不是LinkedList。

参考链接

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

「博客园」ArrayList源码阅读

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1388955.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java零基础教学文档servlet(3)

【AJax】 1.传统开发模式的不足 传统开发模式基于浏览器数据传输功能,页面填写数据/展示数据。浏览器通过访问一个URL地址&#xff0c;将页面的数据提交给服务器。服务器将需要展示的数据返回给浏览器&#xff0c;浏览器再进行数据解析&#xff0c;将数据呈现在用户面前。这种…

QT quick基础:加载资源文件(字体)

一、加载字体 1、准备字体库 Roboto-Regular.ttf 2、在工程下面新建文件夹fonts&#xff0c;并将字体库放到该文件夹下面。 3、在QT Create 工程中添加字体。 添加现有文件选择Roboto-Regular.ttf。 4、执行qmake 5、在.qml文件加载字体 /* 加载字体 */FontLoader {id: f…

如何在 Windows 10 中恢复已删除的文件

几乎每个 Windows PC 用户都曾意外删除过他们想要保留的文件。尽管您的第一步应该是检查回收站&#xff0c;但它可能不在那里。Windows 10 不会自动将所有已删除的文件保留在回收站中。有时它会永久删除文件&#xff0c;让您再也看不到它们。如果您遇到这种情况&#xff0c;我们…

如何使用Docker一键部署WBO白板并实现固定公网地址远程访问

文章目录 前言1. 部署WBO白板2. 本地访问WBO白板3. Linux 安装cpolar4. 配置WBO公网访问地址5. 公网远程访问WBO白板6. 固定WBO白板公网地址 前言 WBO在线协作白板是一个自由和开源的在线协作白板&#xff0c;允许多个用户同时在一个虚拟的大型白板上画图。该白板对所有线上用…

AI数字人短视频变现项目:打造短视频运营变现新模式

随着社交媒体和短视频平台的兴起&#xff0c;越来越多的人开始关注如何将短视频变现。在这个时代&#xff0c;创新和科技成为了推动变现模式发展的关键。AI数字人作为一种全新的创新形式&#xff0c;正在迅速进入人们的视野。本文将介绍AI数字人短视频变现项目&#xff0c;以及…

新晋中科院TOP,不到3个月出结果,编辑处理效率真心高!

【SciencePub学术】 Measurement 期刊评说 网 友 辣 评 评说1&#xff1a;不到三个月出结果&#xff0c;挺快的&#xff0c;期刊效率高&#xff0c;2023年12月27日期刊更新成TOP了&#xff0c;值得推荐&#xff01; 评说2&#xff1a;一般送审了就问题不大&#xff0c;超过…

公众号申请数量已超上限的解决方法

一般可以申请多少个公众号&#xff1f; 公众号申请限额在过去几年内的经历了很多变化。对公众号申请限额进行调整是出于多种原因&#xff0c;确保公众号内容的质量和合规性。企业公众号的申请数量从50个到5个最后到2个&#xff0c;对于新媒体公司来说&#xff0c;这导致做不了…

分类预测 | Matlab实现CS-SVM布谷鸟算法优化支持向量机的数据分类预测

分类预测 | Matlab实现CS-SVM布谷鸟算法优化支持向量机的数据分类预测 目录 分类预测 | Matlab实现CS-SVM布谷鸟算法优化支持向量机的数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现CS-SVM布谷鸟算法优化支持向量机的数据分类预测。 2.自带数据…

用Perl采集美容化妆目标网站做一个深度调研

在Perl中编写爬虫程序涉及到几个关键步骤&#xff0c;包括使用相关的库来发送HTTP请求和解析HTML内容。首先我们要了解Perl爬虫程序编程得几大步骤&#xff1a;安装必要的Perl模块—创建一个用户代理—发送HTTP请求—解析响应内容—提取所需数据—存储或进一步处理数据。所以说…

设计模式之开闭原则:如何优雅地扩展软件系统

在现代软件开发中&#xff0c;设计模式是解决常见问题的最佳实践。其中&#xff0c;开闭原则作为面向对象设计的六大基本原则之一&#xff0c;为软件系统的可维护性和扩展性提供了强大的支持。本文将深入探讨开闭原则的核心理念&#xff0c;以及如何在实际项目中运用这一原则&a…

2024山东省“信息安全管理与评估“---内存取证(高职组)

2024山东省“信息安全管理与评估“—内存取证(高职组) PS:需要环境私信博主 内存取证: 任务环境说明: 攻击机:kali 物理机:Windows 任务说明:本次需要检测的镜像已放置放在本机桌面上。 这里想学取证的小伙伴可以参考:http://t.csdnimg.cn/EHwpu 1.从内存中获取到用户…

在公网服务器搭建CobaltStrike

FLAG&#xff1a;自律是对抗悲伤的唯一出路 专研方向: 服务器Centos&#xff0c;CS渗透神器 每日emo&#xff1a;04年的猴&#xff0c;过的怎么样了 欢迎各位与我这个菜鸟交流学习 在公网服务器搭建CobaltStrike&#xff1a; 之前玩cs都是在局域网&#xff0c;准备积累以下战…

spring常见漏洞(3)

CVE-2017-8046 Spring-Data-REST-RCE(CVE-2017-8046)&#xff0c;Spring Data REST对PATCH方法处理不当&#xff0c;导致攻击者能够利用JSON数据造成RCE。本质还是因为spring的SPEL解析导致的RCE 影响版本 Spring Data REST versions < 2.5.12, 2.6.7, 3.0 RC3 Spring Bo…

国自然热点|超级增强子“super”在哪?cell重磅发现:新型DNA调控元件——促进子

增强子&#xff08;enhancer&#xff09;&#xff0c;又可称为强化子&#xff0c;是DNA上一段可与蛋白质&#xff08;反式作用因子&#xff0c;trans-acting factor&#xff09;结合的区域&#xff0c;可以被转录因子等蛋白结合从而激活基因转录。1981年&#xff0c;增强子首次…

中仕公考:2024年度国考笔试分数公布,进面名单已出

2024年度考试录用公务员笔试成绩和合格分数线已经公布&#xff0c;考生们可以自行登录公务员专题网站查询成绩。 进面人员名单根据规定的面试比例&#xff0c;按照笔试成绩从高至低的顺序&#xff0c;1月14日已经公布进面名单。 没有进入面试人员名单的考生可以关注调剂&…

上海亚商投顾:沪指冲高回落 旅游板块全天强势

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日冲高回落&#xff0c;创业板指跌近1%&#xff0c;北证50指数跌超3%。旅游、零售板块全天强势&#xf…

智能代码:生成式 AI 在软件开发中的革命性角色

想象一下&#xff0c;在智能手机革命性地改变了我们的生活之后&#xff0c;现在轮到了生成式 AI 在软件开发领域掀起风暴。你知道吗&#xff0c;如果代码能自己编写自己&#xff0c;这将是多么惊人的一步&#xff1f;这就好比我们现在能轻松地用手机应用管理日常生活一样&#…

vs2022配置OpenCV测试

1&#xff0c;下载Opencv安装包 OpenCV官网下载地址&#xff1a;Releases - OpenCV 大家可以按需选择版本进行下载&#xff0c;官网下载速度还是比较慢的&#xff0c;推荐大家使用迅雷进行下载 下载安装包到自定义文件夹下 双击安装 按以下图示进行安装 2、 添加环境变量 打…

Vue v-model 详解

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…

精品基于Uniapp+springboot车辆充电桩缴费管理系统管理系统App-地图

《[含文档PPT源码等]精品基于Uniappspringboot充电桩管理系统App》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;Java 后台框架&#xff1a;springboot、ssm 安…