ArrayList集合源码解读(一)

news2024/9/24 13:15:34

ArrayList集合源码解读(一)

前言

笔者在阅读网上众多的ArrayList源码解读时发现他们都是以1.8版本的来进行讲解,并且很多都是囫囵吞枣,看的人一脸懵逼。

其实现在的很多公司都换成了17版本的jdk。笔者决定自己写一个ArrayList集合源码的解读,笔者将直接通过17版本的jdk深入底层的带大家学习ArrayList。17版本的jdk大部分代码和1.8是一样的,只要极少部分存在漏洞的代码进行了优化。所以大家学完17版本的后,1.8版本的肯定都可以看懂,并且各位还能发现1.8的一些实现漏洞。


ArrayList 底层是基于 Object[] 数组实现的。与 Java 中的静态数组相比,它的容量能动态增长。可以理解为 ArrayList 是一种动态数组。那么今天我们就来阅读下源码,看看 Java 是如何实现这个动态数组的集合。

注意:ArrayList 适用于频繁的查找工作,是一个线程不同步的集合,这点他与 LinkedList 相同。


首先,我们照例看一下 ArrayList 的类定义

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

一. 继承和实现关系

ArrayList 类继承了 AbstractList 抽象类。

  • 我们可以点进源码看一下作者对 AbstractList 抽象类的描述:此类提供了List接口的骨架实现,以最大限度地减少实现此接口所需的工作量。

ArrayList 实现了 List , RandomAccess , Cloneable , Serializable 这些接口。

  • List : **表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。 **

  • RandomAccess这是一个标志接口,表明实现这个接口的 List 集合支持 快速随机访问

    • 什么是快速随机访问? -> 可以通过索引下标快速访问元素就叫做快速随机访问
  • CloneableCloneable 是一个标记接口,我们点进去会发现它并没有任何方法。此接口表明ArrayList具有拷贝能力,可以进行深拷贝或浅拷贝操作 。

    package java.lang;
    
    public interface Cloneable {
    }
    
  • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
    在这里插入图片描述


二. 核心源码解读

笔者以 JDK17 为例,分析一下 ArrayList 的底层源码。

2.1. 属性及构造函数解读

ArrayList 提供了三个构造方法帮助我们创建对象。我们来详细分析下这三个构造方法实现的底层逻辑。

  • ArrayList(int initialCapacity)通过给定的 initialCapacity 创建集合对象。
  • ArrayList()无参构造方法,创建一个初始值为空数组的集合对象。
    • 注意: 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10
  • ArrayList(Collection<? extends E> c)传入一个集合,构造一个包含此集合中所有元素的集合对象。

源码:

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

    // 空数组(用于空实例)
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //用于默认大小空实例的共享空数组实例。
    //我们把它从 EMPTY_ELEMENTDATA 数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 保存ArrayList数据的数组
    transient Object[] elementData; // non-private to simplify nested class access

    // 元素个数
    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);
        }
    }

    /**
     *  默认无参构造函数
     */
    public ArrayList() {
        // 初始化为一个空数组
        // 在添加第一个元素时,会将容量扩容为 10  (后面笔者会专门讲解扩容源码,各位有个印象即可)
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 传入一个集合,构造一个包含此集合中所有元素的列表,按照它们由集合的迭代器返回的顺序。
     */
    public ArrayList(Collection<? extends E> c) {
        // 将指定集合转换成数组
        Object[] a = c.toArray();
        // (size = a.length) != 0 做了两个操作
        // 1.将 a.length 的值赋值给 size
        // 2.判断 size 是否不等于 0
        if ((size = a.length) != 0) {
            // 如果 elementData 是 ArrayList 类型数据,就直接赋值
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                // 如果不是则转换成 Object[] 数组再赋值(因为 ArrayList 底层是 Object[] 数组)
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 如果给定集合 c 的长度为 0,则替换成空数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }

2.2 核心扩容方法解读

ArrayList 的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList 的扩容机制避免了这种情况。

源码:

 /**
  * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
  *
  * @param minCapacity 所需的最小容量
  */
public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length // 要保证传入的 minCapacity 大于当前集合中数据长度
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA  // elementData不能为空
             && minCapacity <= DEFAULT_CAPACITY)) {  // 传入的 minCapacity 要大于默认长度10
        modCount++;  // 版本号控制
        // 扩容底层实现方法,调用此方法代表已经开始扩容了
        grow(minCapacity);
    }
}


/**
 * 增加容量,以确保它至少可以容纳最小容量参数指定的元素数量
 *
 * @param minCapacity 所需的最小容量
 * @throws OutOfMemoryError 如果 minCapacity 小于 0,抛出异常
 */
private Object[] grow(int minCapacity) {
    // 获取当前集合中已有的元素个数
    // oldCapacity为旧容量,newCapacity 为新容量
    int oldCapacity = elementData.length;
    // 如果 oldCapacity > 0 或者 集合中的元素不为空数组,则进行扩容
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 扩容的主体代码,其实简单来说就是将新数组扩容成老数组的 1.5 倍
        // 大家要知道位运算的速度远远快于整除运算,所以看似炫技的写法 oldCapacity >> 1 其实是为了性能考虑
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                          minCapacity - oldCapacity,  // 判断要增加几个元素位置
                          oldCapacity >> 1);  // >> 1 等价于 / 2  eg: 5 >> 1 = 5 / 2 = 3
        // 将原数据拷贝到新数组长度的数组中
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        // 进入 else 则证明我们的 oldCapacity <= 0 或者 elementData 为u空数组,则进行下方的操作
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}


/**
* 要分配的最大数组大小
*/
 public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;


/**
* 根据给定的参数返回一个新长度
*
* @param oldLength   数组的当前长度(必须为非负)
* @param minGrowth   所需的最小增长量(必须为正)
* @param prefGrowth  首选增长量
* @return 新数组长度
* @throws OutOfMemoryError 如果新长度超过 Integer.MAX,报错
*/
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // 大家看上方的官方解释可能很懵逼。什么是所需的最小增长量?什么是首选增长量?
    // 其实通俗的理解,就是比较 当前数组的长度的一半 (oldLength >> 1) 和 我们期望增加的长度 (minCapacity - oldCapacity) 谁更大,就用谁
    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); 
    // 合法性检测   SOFT_MAX_ARRAY_LENGTH:要分配的最大数组大小
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        // else 中的逻辑,此处涉及不到,感兴趣的同学可以自己去研究下
        return hugeLength(oldLength, minGrowth);
    }
}

2.3 常用方法解读

未完待续~

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

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

相关文章

网工内推 | 国企运维工程师,华为认证优先,最高年薪20w

01 上海陆家嘴物业管理有限公司 &#x1f537;招聘岗位&#xff1a;IT运维工程师 &#x1f537;岗位职责&#xff1a; 1、负责对公司软、硬件系统、周边设备、桌面系统、服务器、网络基础环境运行维护、故障排除。 2、负责对各部门软件操作、网络安全进行检查、指导。 3、负责…

14.Lambda表达式、可变参数

一.Lambda表达式 1.1 函数式接口 1.什么是函数式接口 在Java中&#xff0c;函数式接口是指只包含单个抽象方法的接口&#xff0c;但它也可以有其他方法&#xff0c;例如默认方法和静态方法。函数式接口可以使用Lambda表达式或方法引用来创建该接口的实例。Java 8引入了函数式…

分享一个基于SpringBoot的大学生创新能力培养平台Java(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

七夕表白代码包

目录 1.像素爱心代码 2.心动爱心代码 3.问答样式代码 1.像素爱心代码 今年的最火的当然是像素风&#xff0c;一个一个小方块拼成的爱心超级可爱。 (1)桌面新建一个文本档.txt (2)输入以下代码,可以直接复制 <!DOCTYPE html><html><head><meta chars…

Vue项目通过宝塔部署之后,页面刷新后浏览器404页面

目录 报错 解决方法 报错 将vue项目在宝塔上部署&#xff0c; 当项目挂载到服务器上去&#xff0c;进行浏览器的访问&#xff0c;是能正常访问的&#xff0c;可是当我们在浏览器上进行刷新之后&#xff0c;浏览器会给我们返回一个404的页面。 解决方法 &#xff08;1&#…

如何利用 LNMP 搭建 WordPress 站点

作者 乐维社区&#xff08;forum.lwops.cn&#xff09; 许远 在这个信息爆炸的时代&#xff0c;拥有一个能够迅速传达信息、展示个性、并能够与世界互动的在线平台&#xff0c;已成为企业和个人的基本需求。WordPress&#xff0c;以其无与伦比的易用性和强大的扩展性&#xff0…

Redis5-缓存

目录 什么是缓存 添加Redis缓存 缓存更新策略 三种策略 数据库和缓存不一致的解决方案 缓存穿透 缓存雪崩 缓存击穿 缓存工具封装 什么是缓存 缓存是数据交换的缓冲区&#xff08;Cache&#xff09;&#xff0c;是存贮数据的临时地方&#xff0c;一般读写性能较高 多…

【机器学习】BP神经网络中的链式法则

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 BP神经网络中的链式法则1. 引言2. 链式法则基础2.1 什么是链式法则&#xff1f;…

springboot mybatis plus 固定查询条件及可选查询条件的组合查询,使用QueryWrapper.and()来解决。

1、我们在写查询SQL的时候&#xff0c;经常会碰到&#xff0c;比如&#xff0c;同一个类别下的某一个编号的物料信息&#xff0c;或者是同一批次的物料库存问题等等。 所属类别fid物料编号bm物料批次pc110.01.0220240807110.01.0320240807 210.02.0120240805 2、那么我…

定点数的运算

目录 1.定点数的移位运算 1.1算数移位 数学含义&#xff1a; 规律总结&#xff1a; 1.2逻辑移位 1.3循环移位 不带进位位 带进位位 2.定点数的加减运算 3.定点数的乘除运算 3.1原码 一位乘法 除法 3.2补码 一位乘法 除法 1.定点数的移位运算 1.1算数移位 数学…

org.gitlab4j使用报错问题

报错如上&#xff0c;刚开始报错Caused by: java.lang.NoClassDefFoundError: javax/ws/rs/core/StreamingOutput。 原因&#xff1a;项目是JDK17引起的版本不兼容 解决&#xff1a;升级高版本即可。

4.1 图标资源、光标资源

图标资源 添加资源 添加资源(可视化完成) 注意图标的大小&#xff0c;一个图标文件中&#xff0c;可以有多个不同大小的图标。加载 LoadIcon 是 Windows API 中用于加载图标资源的函数 HICON WINAPI LoadIcon(HINSTANCE hInstance,LPCTSTR lpIconName );参数说明 1.hInstanc…

牛客JS题(二十七)Getter

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; class基础用法getter的应用setter的应用 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charsetutf-8></head><body><script type"text/javascript&qu…

机器人帮助文档

文章目录 机器交流使用群使用图例1. 查看机器人使用文档2. 直接问问题&#xff08;系统默认AI&#xff09;3. 系统默认AI切换4. 直接问问题&#xff08;指定讯飞星火AI&#xff09;5. 直接问问题&#xff08;指定百度文心AI&#xff09;6. 直接问问题&#xff08;指定谷歌AI&am…

Python的全局变量

我来举个例子 像下面&#xff0c;我把全局变量写在函数外面&#xff0c;导致func函数里得不到变量 正确做法应该是在函数内引入全局变量&#xff0c;利用global关键字 请注意&#xff01;由于1的操作导致全局变量发生改变&#xff0c;可能会影响到其他引用全局变量的地方。这点…

基于统计检验与机器学习研究客户对保险兴趣的因素

1.项目背景 保险单是一种安排&#xff0c;公司承诺为特定的损失、损坏、疾病或死亡提供赔偿保证&#xff0c;以换取支付指定的保费。保费是客户需要定期向保险公司支付的一笔钱&#xff0c;以提供此保证&#xff0c;与医疗保险一样&#xff0c;也有车辆保险&#xff0c;客户每…

【Python】requests获取网络响应的时候,遇到url超过最大重试次数的解决方法

我们在使用requests连接网址后&#xff0c;获取网络响应的时候&#xff0c;有时候可能会遇到这样的问题&#xff1a; 问题&#xff1a; Maxretries exceeded with url: /tags-%E9%A1%B9%E7%9B%AE-5.html(Caused by SSLError(SSLEOFError(8,‘EOFoccurred in violation of prot…

大模型层数过多影响

当层数过多时候&#xff0c;梯度是累乘关系&#xff0c;如100 最后可能超过f16精度 梯度爆炸 后面梯度和权重值特别大 梯度消失 后台梯度和权重趋近于0 梯度合理范围e-6 到 e3 优化方法 1、优化点 乘法改为加法 resnet lstm 2、归一 梯度归一&#xff0c;大于小于阈值…

防范 Active Directory 攻击

关注公众号网络研究观获取更多内容。 Active Directory (AD) 是组织 Windows 网络的核心&#xff0c;可默默协调用户访问、身份验证和安全性。 但您真的了解它的工作原理吗&#xff1f;本博客将揭开 AD 的层层面纱&#xff0c;揭示其核心组件以及它们如何实现集中控制。探索 …

【ARM CoreLink 系列 4.2 -- NIC-400 控制器详细介绍】

请阅读【ARM AMBA 总线 文章专栏导读】 文章目录 NIC-400 TopNIC-400 Terminology(术语介绍)AMBA Slave Interface BlockSwitchDefault SlaveInterface BlockGlobal Programmers ViewAMBA Master interface BlockNIC-400 GPV(Global Programmers View)NIC-400 SwitchNIC-400 …