Java:ArrayList源码解析

news2024/11/24 6:43:52

Java:ArrayList源码解析

在这里插入图片描述

导言

我们都知道ArrayList是一个可以实现自动扩容的List类,为了理解ArrayList是如何进行扩容的,我们就有必要对ArrayList的源码进行分析。本文中将着重研究源码中ArrayList是如何实现自动扩容的。

源码解析

ArrayList的初始化

我们从构造函数入手,ArrayList有三种构造函数:

  • ArrayList(int initialCapacity):指定初始容量
  • ArrayList():默认的构造函数
  • ArrayList(Collection<? extends E> c):指定初始的数据集

可以看到第一个构造函数指定的是ArrayList的初始容量,我们来看看这个方法:

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

这里就涉及到了ArrayList中的几个成员变量:

  • transient Object[] elementData:实际上存储元素的容器,ArrayList的容量就是这个数组缓冲区的容量。
  • private static final Object[] EMPTY_ELEMENTDATA = {}:当容量指定为0时,ArrayList将直接用这个数组来充当存储数据的容器。
  • private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}:当初始化时什么都没有指定时,ArrayList将直接使用这个数据充当存储数据的容器。

除此之外,其实这个方法也没有什么特殊之处,只是根据指定容量是否为零确定了存储元素的容器,可以看到是使用的Object数组。

接着我们来看第二个构造方法:

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

这个方法更简单,当我们什么都没有指定时就会确定存储元素的数据为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这个我们在前面已经介绍过了。

第三个构造方法:

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

这个构造方法接受一个集合,并会将其作为ArrayList的初始元素。可以看到默认情况下是直接将传入的集合转化为数组后赋值给elementData,也就是将其作为存储元素的容器。如果初始数据长度不为0的话还会对转化而来的数组进行一次额外的检查(防止返回的不是Object类型的数组)。初始数据长度为0则和第一个构造方法传入0值一样处理。

向ArrayList中添加元素

我们向ArrayList添加元素当然是调用add方法,所以接下来看add方法:

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

首先会调用ensureCapacityInternal方法,看名字也可以知道这个方法是用来对ArrayList的数据容器进行扩容的,会确保有足够的空间存储加入的元素。之后将这个元素存储到数据存储区elementData中,如果添加成功就会返回true。

ArrayList的扩容

上面说到会调用ensureCapacityInternal方法来进行扩容,所以接下来看这个方法的逻辑:

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

    ensureExplicitCapacity(minCapacity);
}

首先会确认之前的数据存储区是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,还记得它吗,如果调用不带任何参数的构造函数的话elementData数据存储区就会指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA。也就是说如果在本次添加以前该ArrayList中没有存储任何数据的话就会进入第一个if块中,然后会比较出最小的容量将其赋值给minCapacity,主要是比较传入的容量值和DEFAULT_CAPACITY这两个常量的大小,并选出较小值。这个DEFAULT_CAPACITY的值是10:

在这里插入图片描述

这个确定出来的minCapacity是用来传导到接下来的方法中继续进行扩容过程的。这也就是我们经常会听到的ArrayList的最小初始容量的说法来源,因为默认情况下最小扩容量肯定是比这个DEFAULT_CAPACITY,也就是10大的。确定完minCapacity的值之后继续将其传递给下面的方法接着进行扩容:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

这里出现了一个新的成员变量modCount,这个变量是用来记录ArrayList的结构性修改的次数的,结构修改是那些改变列表大小或以某种方式干扰列表的操作,以至于正在进行的迭代可能产生不正确的结果。具体来说,迭代器和列表迭代器的实现(通过iteratorlistIterator方法返回)使用了这个字段。如果该字段的值意外更改,迭代器(或列表迭代器)将在响应nextremoveprevioussetadd等操作时抛出ConcurrentModificationException异常。这提供了"快速失败"(fail-fast)的行为,而不是在迭代过程中出现并发修改时出现不确定性行为。

总的来说这个modCount的主要目的是为了保证在迭代期间检测到并发修改,从而确保迭代器的安全性。

在这个方法中首先对该字段进行自增,然后会判断需要的最小容量和当前数据存储区的容量,如果当前数据存储区的容量不足的话调用grow方法继续进行扩容:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    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);
}

其实还是挺简单的:

  • 首先,它获取当前 ArrayList 的容量,即 elementData.length
  • 然后,它计算新容量 newCapacity,通常是旧容量的 1.5 倍(oldCapacity + (oldCapacity >> 1)),这是一种常见的扩容策略,旨在在不过度浪费内存的情况下提供足够的空间。
  • 接下来,它检查新容量是否仍然小于 minCapacity,如果是,则将新容量设置为 minCapacity,以确保容量足够。
  • 最后,它检查新容量是否超过了 ArrayList 可以容纳的最大数组大小(MAX_ARRAY_SIZE),如果超过了,则调用 hugeCapacity 方法来计算一个合适的容量。
  • 最终,它使用 Arrays.copyOf 方法将 elementData 数组的大小扩展为新容量,并将新数组赋给 elementData,从而完成了扩容操作。

访问ArrayList的元素

这里访问ArrayList有多种方法,首先就是直接调用get方法:

public E get(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    return (E) elementData[index];
}

这个很简单,没什么好说的。接下来看用迭代器访问的方式。我们要使用迭代器的话首先就需要获取迭代器:

public Iterator<E> iterator() {
    return new Itr();
}

可以看到默认返回的是Itr类,Itr类是ArrayList里的内部类,迭代器里重要的一点就是保证安全,也就是我们之前提到过的modCount参数有关的。来看一部分:

private class Itr implements Iterator<E> {
    // Android-changed: Add "limit" field to detect end of iteration.
    // The "limit" of this iterator. This is the size of the list at the time the
    // iterator was created. Adding & removing elements will invalidate the iteration
    // anyway (and cause next() to throw) so saving this value will guarantee that the
    // value of hasNext() remains stable and won't flap between true and false when elements
    // are added and removed from the list.
    protected int limit = ArrayList.this.size;

    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor < limit;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        int i = cursor;
        if (i >= limit)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
            limit--;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;

        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

可以看到就将之前的modCount赋值给了expectedModCount,在之后的next方法和remove方法中,一旦modCountexpectedModCount不匹配就会抛出异常,说明该ArrayList已经被并发修改过了,并不安全可靠了。

总结

最后让我们总结一下ArrayList,首先它是一个可以实现自动扩容的容器。它的默认最小容量为10,每当当前容量已经无法容纳下新加入的元素时就会进行扩容。关于扩容,ArrayList会在修改数据存储区之前自增modCount的值,这个值是为了判断线程安全,实现Fast-Fail用的。

最后附上一张流程图:

在这里插入图片描述

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

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

相关文章

合宙Air724UG LuatOS-Air LVGL API控件-页面 (Page)

页面 (Page) 当控件内容过多&#xff0c;无法在屏幕内完整显示时&#xff0c;可让其在 页面 内显示。 示例代码 page lvgl.page_create(lvgl.scr_act(), nil) lvgl.obj_set_size(page, 150, 200) lvgl.obj_align(page, nil, lvgl.ALIGN_CENTER, 0, 0)label lvgl.label_crea…

netbeans+wamp+php配置调试代码

netbeans版本&#xff1a;8.1 wamp版本是2.5,里面的php5.5.12&#xff0c;apache2.4.9&#xff0c;mysql5.6.17。 wamp已经自带Xdebug&#xff08;PHP程序调试器&#xff09;。 步骤&#xff1a; 1.路径&#xff1a;D:\wamp\bin\apache\apache2.4.9\bin\php.ini 2.打开ne…

MyBatis 动态 SQL 实践教程

一、MyBatis动态 sql 是什么 动态 SQL 是 MyBatis 的强大特性之一。在 JDBC 或其它类似的框架中&#xff0c;开发人员通常需要手动拼接 SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。例如&#xff0c;拼接时要确保添加了必要的空格&#xff0c;还要注意去掉列…

电脑上jpg图片怎么改大小kb?三招学会图片压缩

在电脑上处理jpg图片时&#xff0c;有时候我们需要修改图片的大小&#xff0c;尤其是在需要上传图片到一些限制大小的平台时。那么&#xff0c;如何更改jpg图片的大小呢&#xff1f;今天&#xff0c;我将分享三个实用的方法&#xff0c;让大家轻松解决图片太大的问题&#xff0…

仿射密码 affine

参考链接&#xff1a;https://www.cnblogs.com/0yst3r-2046/p/12172757.html 仿射加密法 在仿射加密法中&#xff0c;字母表的字母被赋予一个数字&#xff0c;例如 a0&#xff0c;b1&#xff0c;c2…z25 。仿射加密法的密钥为0-25直接的数字对。 仿射加密法与单码加密法没什么…

ubuntu修改用户名和用户密码

1.修改passwd文件 sudo gedit /etc/passwd2.修改shadow文件 sudo gedit /etc/shadow3.修改home目录下文件夹名 mv /home/原用户名/ /home/新用户名4.修改sudo权限&#xff08;修改group用户组&#xff09; sudo gedit /etc/group5.修改用户密码 sudo passwd username #修改…

位运算 |(按位或) (按位与) ^(按位异或)

目录 文章目录&#xff1a;本章讲解的主要是刷题系列 1&#xff1a;首先会介绍 I & ^这三个操作符的作用&#xff0c;性质 2&#xff1a;三道使用位运算操作符的经典 笔试题(来自剑指offer) 题目链接如下&#xff1a; 1:136. 只出现一次的数字 - 力扣&#xff08;LeetCode…

Jmeter进阶使用指南-使用断言

Apache JMeter是一个流行的开源负载和性能测试工具。在JMeter中&#xff0c;断言&#xff08;Assertions&#xff09;是用来验证响应数据是否符合预期的一个重要组件。它是对请求响应的一种检查&#xff0c;如果响应不符合预期&#xff0c;那么断言会标记为失败。 以下是如何在…

Echarts 中国地图

直接展示效果图&#xff1a; 我们需要引入两个文件&#xff1a; echarts.js 官网地址下载&#xff1a;快速上手 - Handbook - Apache ECharts chain.js 这个官网已经找不到了&#xff0c;需要自行搜寻下载 也可以私信我(网上下载的China.js会导致省名称定为不准确&#xff0…

Go语言最全面试题,拿offer全靠它,附带免积分下载pdf

面试题文档下链接点击这里免积分下载 go语言入门到精通点击这里免积分下载 文章目录 Go 基础类GO 语言当中 NEW 和 MAKE 有什么区别吗&#xff1f;PRINTF(),SPRINTF(),FPRINTF() 都是格式化输出&#xff0c;有什么不同&#xff1f;GO 语言当中数组和切片的区别是什么&#xf…

【C++】函数重载 ③ ( 为函数指针赋值重载函数 )

文章目录 一、函数指针回顾1、函数指针概念2、函数指针语法3、代码示例 - 函数指针示例 二、为函数指针赋值重载函数1、为函数指针赋值重载函数2、代码示例 - 为函数指针赋值重载函数 博客总结 : 重载函数 : 使用 相同 的 函数名 , 定义 不同 的 函数参数列表 ;判定标准 : 只有…

判断计算机处理器的大小端

一、什么是大小端&#xff1f; 大端模式&#xff1a;低位字节存在高地址&#xff0c;高位字节存在低地址 小端模式&#xff1a;高位字节存在高地址&#xff0c;低位字节存在第地址 二、如何判断计算机处理器是大端还是小端&#xff1f; ①使用变量 x 的值本身来检查处理器存储…

九章云极DataCanvas公司参与大模型重点项目合作签约,建设产业集聚区

9月3日&#xff0c;2023中国国际服务贸易交易会石景山国际开放合作论坛在石景山首钢园成功举办&#xff0c;北京市委常委、常务副市长夏林茂&#xff0c;商务部服务贸易和商贸服务业司司长王东堂&#xff0c;北京市石景山区委书记常卫出席论坛并致辞。论坛期间正式举行“石景山…

关于内涝积水的那些事儿

内涝积水是指城市区域在强降雨、排水系统失效或不足的情况下&#xff0c;大量降雨无法及时排除而积聚在地面上的现象。内涝积水不仅给城市环境和居民的生活带来困扰&#xff0c;同时也给社会经济、交通运输和居民安全造成了严重的危害。 内涝积水的影响 1.内涝积水会对城市环境…

亚马逊鲲鹏系统有哪些优势特点

亚马逊鲲鹏系统是一款全自动化操作的软件&#xff0c;有批量注册账号、自动养号、点击广告、根据关键词进行加购、下单购买、留评、评论点赞或举报、QA等&#xff0c;是一款功能比较齐全的软件。 亚马逊鲲鹏系统有以下优势特点&#xff1a; 1.功能齐全 亚马逊鲲鹏系统一套软件…

【买华为云产品,返CSDN余额红包】,快来薅羊毛!

华为云828营销季火热进行中&#xff0c;9月15日前首次购买华为云产品官网任意一款产品&#xff0c;可获得相应比例的CSDN红包。 热门产品云服务器、域名、商标、主机安全等产品都在其中&#xff0c;任君挑选。 活动优惠价购买后还是获得相应比例余额红包&#xff0c;实际付费金…

nodejs+vue+elementui高校人事管理系统

总体设计 根据高校人事管理系统的功能需求&#xff0c;进行系统设计。 用户功能&#xff1a;用户进入系统可以实现首页、个人中心、职称申报管理、工资信息管理、绩效信息管理、奖惩信息管理、招聘管理等进行操作&#xff1b; 院长功能&#xff1a;院长进入系统可以实现首页、个…

用通俗易懂的方式讲解大模型分布式训练并行技术:概述

近年来&#xff0c;随着Transformer、MOE架构的提出&#xff0c;使得深度学习模型轻松突破上万亿规模参数&#xff0c;传统的单机单卡模式已经无法满足超大模型进行训练的要求。因此&#xff0c;我们需要基于单机多卡、甚至是多机多卡进行分布式大模型的训练。 而利用AI集群&a…

vue中实现签名画板

特意封装成了一个组件&#xff0c;签名之后会生成一张图片 signBoard.vue <template><el-drawer title"签名" :visible.sync"isShowBoard" append-to-body :show-close"false" :before-close"closeBoard" size"50%&quo…

一文1000字彻底搞懂Web测试与App测试的区别

总结分享一些项目需要结合Web测试和App测试的工作经验给大家&#xff1a; 从功能测试区分&#xff0c;Web测试与App测试在测试用例设计和测试流程上没什么区别。 而两者的主要区别体现在如下几个方面&#xff1a; 1 系统结构方面 Web项目&#xff0c;B/S架构&#xff0c;基于…