面试突击:ArrayList源码详解

news2024/11/23 15:37:11

本文已收录于:https://github.com/danmuking/all-in-one(持续更新)

前言

哈喽,大家好,我是 DanMu。ArrayList 是我们日常开发中不可避免要使用到的一个类,并且在面试过程中也是一个非常高频的知识点,这篇文章将深入分析 ArrayList 的底层原理,助你彻底掌握 ArrayList相关的知识点。
ceeb653ely8gszdwumcnyj20u00u0q4v.jpg

数据结构

ArrayList 的底层是数组,与 Java 中的数组不同的是它的容量能动态增长,当空间不足时,ArrayList 将在底层自动增加数组空间,因此相对数组来说使用起来更加方便。

类图

arraylist-class-diagram.png从继承关系上看 ArrayList 继承于 AbstractList,实现了 List, RandomAccess , Cloneable , java.io.Serializable 等接口。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

}
  • List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
  • RandomAccess :这个接口表示这个类具有随机访问的能力。
  • Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
  • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输。

什么是随机访问?
简单来说,就是可以在 O(1) 的时间复杂度内根据下标找到对应元素。典型的具有随机访问的数据结构就是数组,而链表查询下标对应元素需要从头结点开始遍历所有节点,需要 O(N) 的时间复杂度,因此链表就不具有随机访问能力。

核心源码解读

成员变量

每个 ArrayList 都维护了一些重要的成员变量,用于 ArrayList 的管理,其中比较重要的变量有:

// 默认初始容量大小
private static final int DEFAULT_CAPACITY = 10;

// 空数组,所有初始化长度为0的Arraylist共用这个数组,减少内存
private static final Object[] EMPTY_ELEMENTDATA = {};

// 用于默认构造函数,创建 ArrayList 时不分配空间,将空间分配推迟到第一次添加元素时
// 需要区分和 EMPTY_ELEMENTDATA 的区别
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 实际保存 ArrayList 数据的数组
// transient关键字表示这部分内容不会被序列化
transient Object[] elementData; // non-private to simplify nested class access

// ArrayList 所包含的元素个数
private int size;

初始化

ArrayList 中主要有三种构造方法:

// 带初始容量参数的构造函数
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //如果传入的参数大于0,创建 initialCapacity 大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果传入的参数等于0,创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //其他情况,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: " +
                initialCapacity);
    }
}

// 默认无参构造函数
// 先用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 初始化, 当插入第一个数据时才扩容为10.
// 这种方式也被称为懒加载
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
    //将指定集合转换为数组
    elementData = c.toArray();
    //如果elementData数组的长度不为0
    if ((size = elementData.length) != 0) {
        // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)
        if (elementData.getClass() != Object[].class)
            //将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 其他情况,用空数组代替
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

扩容机制

add 方法
/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
    // 加元素之前,先调用 ensureCapacityInternal 方法,保证数组有足够的空间
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 这里看到ArrayList添加元素的实质就相当于为数组赋值
    elementData[size++] = e;
    return true;
}
ensureCapacityInternal 方法
// 确保内部容量达到指定的最小容量。
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(
        // 计算完成添加元素所需的最小容量
        calculateCapacity(elementData, minCapacity)
    );
}

// 计算完成添加元素所需的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果当前数组元素为空数组(初始情况),返回默认容量和最小容量中的较大值作为所需容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 否则直接返回最小容量
    return minCapacity;
}

// 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    // 这是ArrayList中维护的一个用来统计结构变化次数的变量,可以忽略
    modCount++;
    // 判断当前数组容量是否足以存储 minCapacity 个元素
    if (minCapacity - elementData.length > 0)
        // 存不下,调用 grow 方法进行扩容
        grow(minCapacity);
}
grow 方法

grow 方法是实现扩容的核心方法:

/**
 * 能分配的最大数组大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * ArrayList扩容的核心方法。
 */
private void grow(int minCapacity) {
    // oldCapacity为旧容量,newCapacity为新容量
    int oldCapacity = elementData.length;
    // 将新容量更新为旧容量的1.5倍
    // 使用位运算,速度更快
    int newCapacity = oldCapacity + (oldCapacity >> 1);

    // 然后检查扩容后容量是否大于需要的容量,若还是小于需要的容量,直接扩容到需要的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;

    // 如果新容量超过最大值,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
    // 处理边界条件
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 对minCapacity和MAX_ARRAY_SIZE进行比较
    // 若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
    // 若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
    // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
流程图

集合.drawio.png

删除元素

ArryList提供了两个删除List元素的方法,分别通过下标和元素进行元素的删除。

通过下标删除元素
public E remove(int index) {
    // 检查下标是否越界
    rangeCheck(index);
    
    modCount++;
    // 取出元素的值
    E oldValue = elementData(index);
    // 计算需要移动元素的个数
    int numMoved = size - index - 1;
    // 如果删除的元素不是最后一位,将数组后面的元素移动到前面
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 将原来的最后一个index设为null
    elementData[--size] = null; // clear to let GC do its work
    // 返回被删除元素
    return oldValue;
}

未命名绘图-第 2 页.drawio.png

通过元素删除元素
public boolean remove(Object o) {
    // 如果 o 为null,就删除 ArrayList中第一个值为 null 的元素
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        //  遍历 ArrayList,如果存在则删除第一个等于 o 的元素
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
// 这边的逻辑和通过下标删除元素的逻辑一样
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

点关注,不迷路

好了,以上就是这篇文章的全部内容了,如果你能看到这里,非常感谢你的支持!
如果你觉得这篇文章写的还不错, 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!
白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !

最后推荐我的IM项目DiTing(https://github.com/danmuking/DiTing-Go),致力于成为一个初学者友好、易于上手的 IM 解决方案,希望能给你的学习、面试带来一点帮助,如果人才你喜欢,给个Star⭐叭!

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

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

相关文章

酷睿Ultra 200V系CPU全阵容规格、跑分出炉:拉了坨大的

从最近几个月 12 代酷睿 CPU 大面积降价清库存&#xff0c;到 13、14 代高端 U 大范围翻车问题迟迟得不到解决。 这就很难不让人生出一种 Intel 似乎真无暇顾及老款 U&#xff0c;而是将有限精力通通放在了接下来酷睿 Ultra 处理器上的感觉。 当然&#xff0c;作为新工艺、新架…

【编译原理必考大题】 推导构建语法树,写出语法树的短语,简单短语和句柄

写在最前 本文为编译原理重点考察大题之一&#xff0c;理论基础见专栏文章&#xff0c;0基础直接使用也可食用 文章目录 推导构造语法树1.语法树的概念2. 子树&#xff0c;短语&#xff0c;简单短语&#xff0c;句柄2.1 子树2.2 短语2.3 简单短语与句柄2.4 真题实战 推导构造语…

Discourse OpenAI 生成图片

正如一些讨论的&#xff0c;生成图片是比较贵的。 差不多到了 1 元 一张图了。 就 OpenAI 生成了上面 4 张图&#xff0c;费用 0.4 美元。 Discourse OpenAI 生成图片 - Discourse - iSharkFly

AI 开发平台(Coze)搭建《AI女友(多功能版本)》

前言 本文讲解如何从零开始&#xff0c;使用扣子平台去搭建《AI女友&#xff08;多功能版本&#xff09;》 bot直达&#xff1a;AI女友&#xff08;多功能版&#xff09; - 扣子 AI Bot (coze.cn) 欢迎大家前去体验&#xff01;&#xff01;&#xff01; 正文 功能介绍 …

分别使用netty和apache.plc4x测试读取modbus协议的设备信号

记录一下常见的工业协议数据读取方法 目录 前言Modbus协议说明Netty 读取测试使用plc4x 读取测试结束语 前言 Modbus 是一种通讯协议&#xff0c;用于在工业控制系统中进行数据通信和控制。Modbus 协议主要分为两种常用的变体&#xff1a;Modbus RTU 和 Modbus TCP/IP Modbus …

平面点云格网过程及可视化介绍(python)

1、背景介绍 实际人工构造物中&#xff0c;很多物体表面为平面结构&#xff0c;因此将点云投影在二维平面上进行处理&#xff0c;如进行点云面积计算、点云边缘提取等。 具体案例可以参考博客&#xff1a;详解基于格网法统计平面点云面积_点云格网法计算xy投影面积-CSDN博客、点…

分页处理封装+分页查询题目列表

文章目录 1.sun-club-common封装分页1.com/sunxiansheng/subject/common/eneity/PageInfo.java2.com/sunxiansheng/subject/common/eneity/PageResult.java 2.sun-club-application-controller1.SubjectInfoDTO.java 继承PageInfo并新增字段2.SubjectController.java 3.sun-clu…

8个Unity开发高手都在用的秘密技巧!

1. 不要重新发明轮子&#xff0c;使用内置的引擎工具 在使用任何引擎时&#xff0c;比如Unity或Unreal Engine&#xff0c;一些开发者&#xff0c;主要是来自计算机科学领域的开发者&#xff0c;可能会倾向于从头开始开发大型算法或结构&#xff0c;而不去了解引擎中是否已经存…

三角洲行动卡顿严重?这样快速解决三角洲行动国服卡顿问题

三角洲行动官方精心设计的游戏地图和敌人布局&#xff0c;加上“曼德尔砖”等目标导向性道具的引入&#xff0c;更是为玩家之间的竞技和争夺增添了无数的变数。每一次的争夺都如同是一场智慧与勇气的较量&#xff0c;让人热血沸腾&#xff0c;无法自拔。在这个战场上&#xff0…

免费可视化工具如何提升工作效率?

免费可视化工具能为我们的工作带来什么好处&#xff1f;在如今数据密集的工作环境中&#xff0c;如何高效地处理和展示数据成为了每个行业的重要任务。传统的工具如Excel虽然强大&#xff0c;但在处理复杂数据和创建高级图表时往往显得力不从心。而免费可视化工具的出现&#x…

Arduino - 光敏传感器

Arduino - Light Sensor Arduino - 光传感器 In this tutorial, we are going to learn: 在本教程中&#xff0c;我们将学习&#xff1a; How light sensor works 光传感器的工作原理How to connect the light sensor to Arduino 如何将光传感器连接到ArduinoHow to progra…

C#udpClient组播

一、0udpClient 控件&#xff1a; button&#xff08;打开&#xff0c;关闭&#xff0c;发送&#xff09;&#xff0c;textbox&#xff0c;richTextBox 打开UDP&#xff1a; UdpClient udp: namespace _01udpClient {public partial class Form1 : Form{public Form1(){Initi…

如何在Windows 11上设置默认麦克风和相机?这里有详细步骤

如果你的Windows 11计算机上连接了多个麦克风或网络摄像头&#xff0c;并且希望自动使用特定设备&#xff0c;而不必每次都在设置中乱动&#xff0c;则必须将首选设备设置为默认设备。我们将向你展示如何做到这一点。 如何在Windows 11上更改默认麦克风 有两种方法可以将麦克…

[游戏开发][UE5]引擎使用学习记录

C Log和蓝图Log C Log 方法 UE_Log(参数1&#xff0c;参数2&#xff0c;参数3) //举例: UE_LOG(LogTemp, Error, TEXT("Log Info: %s"),"Test Log"); 三个参数的作用 参数1&#xff1a;输出窗口归类使用&#xff0c;你写什么它就显示什么 参数2&#x…

网络安全入门必选:十款免费的抓包工具有哪些?

下面给大家推荐几款好用的免费的抓包工具软件&#xff0c;有需要的小伙伴们来了解一下。 1. Wireshark抓包分析工具 4.0.1 Wireshark是一款功能强大的网络协议分析器&#xff0c;可以实时检测和抓取网络通讯数据。它支持多种协议和媒体类型&#xff0c;并具备丰富的显示过滤…

从0-1搭建一个web项目(package.json)详解

本章分析package.json文件详解 本文主要对packge.json配置子文件详解 ObJack-Admin一款基于 Vue3.3、TypeScript、Vite3、Pinia、Element-Plus 开源的后台管理框架。在一定程度上节省您的开发效率。另外本项目还封装了一些常用组件、hooks、指令、动态路由、按钮级别权限控制等…

干货:ANR日志分析全面解析

ANR类型 出现ANR的一般有以下几种类型&#xff1a; 1:KeyDispatchTimeout&#xff08;常见&#xff09; input事件在5S内没有处理完成发生了ANR。 logcat日志关键字&#xff1a;Input event dispatching timed out 2:BroadcastTimeout 前台Broadcast&#xff1a;onReceiver在…

MYSQL 四、mysql进阶 5(InnoDB数据存储结构)

一、数据库的存储结构&#xff1a;页 索引结构给我们提供了高效的索引方式&#xff0c;不过索引信息以及数据记录都是保存在文件上的&#xff0c;确切说时存储在页结构中&#xff0c;另一方面&#xff0c;索引是在存储引擎中实现的&#xff0c;Mysql服务器上的存储引擎负责对表…

eNSP中VRRP的配置和使用

一、基础配置 1.新建拓扑图 2.配置vlan a.CORE-S1 <Huawei>system-view [Huawei]sysname CORE-S1 [CORE-S1]vlan 10 [CORE-S1-vlan10]vlan 20 [CORE-S1-vlan20]vlan 30 b.CORE-S2 <Huawei>system-view [Huawei]sysname CORE-S2 [CORE-S2]vlan 10 [CORE…

君諾外匯:为什么巴菲特现在加倍下注油气股票?油价上涨是主因吗?

近年来&#xff0c;以巴菲特为代表的一些顶级投资者开始在能源领域加大投资力度&#xff0c;特别是油气股票。这一转变引发了广泛关注&#xff0c;特别是在油价上涨的背景下。本文将Juno markets外匯深入分析巴菲特投资策略的变化原因&#xff0c;探讨其在能源市场的布局及未来…