ArrayList 的底层原理和源码分析

news2024/11/28 14:13:23

tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。

推荐:体系化学习Java(Java面试专题)

文章目录

  • 一、简介
  • 二、自动扩容机制
  • 三、add方法的源码分析
  • 四、addAll方法的源码分析
  • 五、set方法的源码分析
  • 六、remove方法的源码分析
  • 七、Fail-Fast机制

一、简介

ArrayList 是 Java 中常用的动态数组实现,它的底层是基于数组实现的。当创建一个 ArrayList 对象时,实际上是创建了一个 Object 类型的数组,初始容量为 10。当添加元素时,如果数组已满,ArrayList 会自动扩容,它会创建一个新的数组,并将原数组中的元素复制到新数组中。

在这里插入图片描述

二、自动扩容机制

它的本质是数组,所以扩容机制,我们想想数组怎么扩容的?那么 ArrayList 就是怎么扩容的。

在ArrayList 的源码中,可以看到 ArrayList 内部维护了一个 Object 类型的数组 elementData,这个数组用于存储 ArrayList 中的元素。在添加元素时,ArrayList 会先判断数组是否已满,如果已满,就会调用 ensureCapacityInternal 方法进行扩容。这个方法会计算出新的容量大小,并创建一个新的数组 newElementData,然后将原数组中的元素复制到新数组中,最后将新数组赋值给 elementData。

在这里插入图片描述

private Object[] elementData;

public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity);
    }
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
    elementData = Arrays.copyOf(elementData, newCapacity);
}

add 方法会先调用 ensureCapacityInternal 方法进行扩容,然后将元素添加到数组中。在 ensureCapacityInternal 方法中,会根据当前数组是否为空来决定是否使用默认容量大小 10,然后再调用 ensureExplicitCapacity 方法进行扩容。在 ensureExplicitCapacity 方法中,会计算出新的容量大小,并调用 grow 方法进行扩容。在 grow 方法中,会计算出新的容量大小,然后调用 Arrays.copyOf 方法创建一个新的数组,并将原数组中的元素复制到新数组中,最后将新数组赋值给 elementData。

三、add方法的源码分析

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 检查容量是否足够,不足则进行扩容
    elementData[size++] = e;  // 插入元素到数组的末尾
    return true;
}

ensureCapacityInternal 方法用于确保ArrayList的容量足够存储新元素。如果当前容量不足,则会进行扩容操作。具体实现可以查看 ensureCapacityInternal 方法的源码;

private void ensureCapacityInternal(int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果需要扩容,则调用grow方法进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 将原数组中的元素复制到新数组中
    elementData = Arrays.copyOf(elementData, newCapacity);
}

在进行扩容时,ArrayList会先将原有数组中的元素复制到一个新的数组中,然后将新数组作为ArrayList的内部数组。这里使用了 Arrays.copyOf 方法进行数组复制操作。

在容量检查和扩容操作之后, add 方法将新元素插入到数组的末尾,并将列表的size属性加1。最后, add 方法返回一个布尔值,表示元素是否成功添加到列表中。

四、addAll方法的源码分析

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 检查容量是否足够,不足则进行扩容
    System.arraycopy(a, 0, elementData, size, numNew);  // 将新元素插入到数组的末尾
    size += numNew;
    return numNew != 0;
}

addAll 方法首先将要添加的元素转换为数组,然后将新数组中的元素插入到ArrayList的内部数组中。具体实现可以查看以下代码:

Object[] a = c.toArray();  // 将要添加的元素转换为数组
int numNew = a.length;
ensureCapacityInternal(size + numNew);  // 检查容量是否足够,不足则进行扩容
System.arraycopy(a, 0, elementData, size, numNew);  // 将新元素插入到数组的末尾
size += numNew;
return numNew != 0;

在进行容量检查和扩容操作之后, addAll 方法使用 System.arraycopy 方法将新数组中的元素插入到ArrayList的内部数组中。这里需要注意的是, System.arraycopy 方法是一个本地方法,它能够快速地将一个数组中的元素复制到另一个数组中。在将新元素插入到ArrayList的内部数组中之后, addAll 方法会更新列表的size属性,并返回一个布尔值,表示元素是否成功添加到列表中。

五、set方法的源码分析

public E set(int index, E element) {
    rangeCheck(index);  // 检查索引是否越界
    E oldValue = elementData(index);
    elementData[index] = element;  // 将新元素设置到指定位置
    return oldValue;
}

set 方法首先会检查指定的索引是否越界,如果越界则会抛出 IndexOutOfBoundsException 异常。具体实现可以查看以下代码:

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

如果索引没有越界,则 set 方法会将指定位置的元素替换为新元素,并返回原有元素的值。具体实现可以查看以下代码:

E oldValue = elementData(index);
elementData[index] = element;
return oldValue;

需要注意的是,在将新元素设置到指定位置之前, set 方法会先获取该位置上原有的元素,并将其保存到一个临时变量中。这是因为, set 方法需要返回原有元素的值。

六、remove方法的源码分析

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);
    elementData[--size] = null;  // 将最后一个元素置为null,方便垃圾回收
    return oldValue;
}

remove 方法首先会检查指定的索引是否越界,如果越界则会抛出 IndexOutOfBoundsException 异常。具体实现可以查看以下代码:

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

如果索引没有越界,则 remove 方法会将指定位置上的元素从ArrayList中移除,并返回原有元素的值。具体实现可以查看以下代码:

modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;

在将指定位置上的元素从ArrayList中移除之前, remove 方法会先获取该位置上原有的元素,并将其保存到一个临时变量中。这是因为, remove 方法需要返回原有元素的值。在移除元素之后, remove 方法会将列表的modCount属性加1,表示对列表的修改次数增加了1。此外, remove 方法还会将被移除元素后面的所有元素向前移动一位,以便填补被移除元素的位置。具体实现可以查看以下代码:

int numMoved = size - index - 1;
if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index, numMoved);

最后, remove 方法将列表的size属性减1,并将被移除的元素置为null,以便垃圾回收。

七、Fail-Fast机制

ArrayList的**快速失败机制(Fail-Fast机制)**指的是在多线程环境下,如果一个线程修改了ArrayList的结构(增加、删除或修改元素),那么其他线程在访问ArrayList时,如果发现modCount属性(记录ArrayList结构修改次数的属性)与自己持有的modCount属性不一致,就会抛出ConcurrentModificationException异常,从而防止多个线程同时修改ArrayList的结构,导致数据不一致的情况发生。

快速失败机制是一种保护措施,它能够及时发现并报告程序中的错误,从而避免因为错误的数据导致程序崩溃或者出现其他异常情况。在ArrayList中,快速失败机制的实现是通过modCount属性和一个迭代器的expectedModCount属性来实现的。每当ArrayList的结构发生变化时,modCount属性就会加1,而每个迭代器的expectedModCount属性则会保存它创建时的modCount属性的值。当一个迭代器在遍历ArrayList时,如果发现expectedModCount和modCount不一致,就会抛出ConcurrentModificationException异常,从而保证了遍历的安全性。

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

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

相关文章

Cookie Session

第一章 会话技术 1.1 什么是会话 web会话可简单理解为&#xff1a;用户开一个浏览器&#xff0c;访问某一个web网站&#xff0c;在这个网站点击多个超链接&#xff0c;访问服务器多个web资源&#xff0c;然后关闭浏览器&#xff0c;整个过程称之为一个会话. 它是指浏览器和服…

URL到页面: 探索网页加载的神秘过程

当我们从浏览器的地址栏输入 URL, 按下回车, 再到最后出现需要的网页界面, 这中间究竟发生了什么, 接下来就一步步进行解析. 主要是如下过程: 输入网址DNS 解析客户端发送 HTPP 请求建立 TCP 连接服务器处理请求, 计算响应, 返回响应浏览器渲染页面关闭连接 本篇中只是概述整…

AUTOSAR-OS的调度机制-调度表(没理解透,继续更新)

什么是调度表&#xff1a; 1. 调度表由一系列按时间先后顺序排序的终结点组成&#xff0c;其中每个终结点都有自己的任务&#xff0c;有的终结点可能是激活一系列的任务&#xff0c;有的是设置一系列的事件&#xff0c;还有的可能是既激活一系列的任务又设置一系列的事件。 调…

数据结构之庖丁解牛八大排序算法,附动图说明过程(上)

目录 一、排序的概念以及应用 二、常见排序算法的实现 1.插入排序 1.1直接插入排序 b.实现直接插入排序 1.2希尔排序&#xff08;缩小增量排序&#xff09; 2.选择排序 2.1直接选择排序 2.2堆排序 3.交换排序 3.1冒泡排序 一、排序的概念以及应用 1.排序的概念 所谓排序&#x…

Mysql数据库--修改root密码的几种方法(忘记密码知道密码)

Mysql数据库--修改root密码的几种方法&#xff08;忘记密码&知道密码&#xff09; &#x1f53b;一、知道密码情况--修改root密码⛳ 1.1 方式1&#xff1a;alter 命令修改⛳ 1.2 方式2&#xff1a;set password命令修改 &#x1f53b;二、忘记密码情况-修改root密码⛳ 2.1 …

华为OD机试真题 Java 实现【找车位】【2023 B卷 100分】,附详细解题思路

一、题目描述 停车场有一横排车位&#xff0c;0代表没有停车&#xff0c;1代表有车。至少停了一辆车在车位上&#xff0c;也至少有一个空位没有停车。 为了防剐蹭&#xff0c;需为停车人找到一个车位&#xff0c;使得距停车人的车最近的车辆的距离是最大的&#xff0c;返回此…

打造高质量视频,创造视觉奇观!Camtasia 2023为你升级!

嘿&#xff0c;伙计&#xff01; 在这个全新版本中&#xff0c;我们迎来了焕然一新的动画控制和更简化的特效制作流程&#xff0c;让创作变得更高效。 不仅如此&#xff0c;全新的背景去除和动画光标功能也让视频拥有全新的视觉体验。让我们先谈谈光标&#xff0c;这个细节或…

计算机视觉:风格迁移

风格迁移 本节将介绍如何使用卷积神经网络&#xff0c;自动将一个图像中的风格应用在另一图像之上&#xff0c;即风格迁移&#xff08;style transfer&#xff09; (Gatys et al., 2016)。 这里我们需要两张输入图像&#xff1a;一张是内容图像&#xff0c;另一张是风格图像。…

Mac使用DBeaver连接达梦数据库

Mac使用DBeaver连接达梦数据库 下载达梦驱动包 达梦数据库 在下载页面随便选择一个系统并下载下来。 下载下来的是zip的压缩包解压出来就是一个ISO文件&#xff0c;然后我们打开ISO文件进入目录&#xff1a;/dameng/source/drivers/jdbc 进入目录后找到这几个驱动包&#x…

Vue 2和Vue 3路由Router创建的区别简记(在main.js文件中引入的区别和router的js文件中创建语法的区别)

Vue 2和Vue 3路由Router创建的区别即Router3.0和Router4.0的创建区别简记 1、版本的搭配&#xff1a; Vue 2到Vue 3的改版升级&#xff0c;同样的带来Vue Router的升级。创建Vue项目之后&#xff0c;我们可以在package.json文件中看到&#xff0c;Vue 2创建的项目往往是与Vue…

C++ | 拷贝文件

C拷贝文件 文章目录 C拷贝文件ANSI-C-WAYPOSIX-WAY (K&R use this in "The C programming language", more low-level)KISS-C-Streambuffer-WAYCOPY-ALGORITHM-C-WAYOWN-BUFFER-C-WAYLINUX-WAY理智的方式C 17Reference欢迎关注公众号【三戒纪元】 列举了几种拷贝…

nestjs超详细从零到零点五详细入门项目搭建过程

nestjs超详细从零到零点五详细入门项目搭建过程 项目完整地址github&#xff0c;修复了一些swagger文档接口&#xff0c;传参显示问题 从零到有搭建一个完整的后台管理系统项目 涉及到的知识 controller控制器provider提供者module模块middleware中间件filter过滤器pipe管道…

简明Python教程

前言&#xff1a;学习《简明 Python 教程》Swaroop, C. H. 著 沈洁元 译 www.byteofpython.info 摘录&#xff0c;方便以后使用查阅。 基础概念 常量 Python中有4种类型的数——整数、长整数、浮点数和复数。 2是一个整数的例子。长整数不过是大一些的整数。3.23和52.3E-4是…

高标准农田信息化管理平台概要设计

1、综合信息一张图系统 通过一张图的形式&#xff0c;可视化直观展示地区土地分布、耕地质量、高标准农田建设情况、灌溉情况、设备分布情况及环境监测数据。农业管理者可在一张图上查看农田相关信息&#xff0c;及时了解农田情况&#xff0c;为农田管理者的精准管理和科学决策…

Axure教程—拖拽获取(中继器+动态面板 )

本文将教大家如何用AXURE中的中继器和动态面板制作拖拽获取效果 一、效果介绍 如图&#xff1a; 预览地址&#xff1a;https://68e5b3.axshare.com 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87874085?spm1001.2014.3001.5503 二、功能介绍 …

OpenMMLab-AI实战营第二期——2-2.基于RTMPose的耳朵穴位关键点检测(Colab+MMPose)

文章目录 1. Colab和Google云端硬盘1.1 建立项目文件和jupyter文件1.2 Colab运行时选择1.3 关联Colab中的文件和Google云端硬盘的文件 2. Colab和MMPose2.1 环境配置2.2 配置文件修改 3. Colab相关知识 视频链接&#xff1a;B站-RTMPose关键点检测-安装MMDetection和MMPose 1.…

一篇文章搞定Java中常用集合的排序方法

目录 Array 数组 List 列表 Collections.sort() 简单类型 复杂对象 类 使用Lambda表达式 Stream API Map 键值对 对 Map 的 Key 进行排序 对 Map 的 Value 进行排序 最近在做算法题的时候&#xff0c;发现排序在大部分题中都不可或缺&#xff0c;今天心血来潮&am…

Vue配置proxy代理,但接口报错2007 bad domain

1、排查proxy代理配置是否有误 排查 proxyTable 对象中配置的 target 是否正确。若正确&#xff0c;那可能就是请求头的问题。 无特殊配置的情况下&#xff0c;请求头是这样子的&#xff1a; Host 和 Referer 是本地地址&#xff0c;如果后端增加 CSRF 防御机制&#xff0c;…

【JDBC:连接MySQL数据库】出现SQL注入的解决办法、什么时候需要使用SQL注入、事务的使用、悲观锁乐观锁

JDBC JDBC是什么&#xff1f; Java Database Connectivity&#xff08;java语言连接数据库&#xff09; java.sql.*;&#xff08;这个包下有很多接口&#xff09; JDBC的本质是什么&#xff1f; JDBC的本质是SUN公司制定的一套接口&#xff08;interface&#xff09; 接口都有…

【C语言】数据以及位运算

位和位运算 C语言中数据的表示方法各种数据类型可表示的数值范围位和CHAR_BITsizeof运算符整型的内部表示无符号整数的内部表示有符号整数的内部表示 位运算位运算符位与运算位或运算位异或运算位取反运算位左移运算符位右移运算符逻辑位移与算术位移 C语言中数据的表示方法 各…