Java ArrayList扩容机制原理深入分析

 更新时间:2023年02月22日 11:35:51   作者:绿仔牛奶_  
在Java中,ArrayList是最常用的集合之一。它是一种容器,它的内部定义了一个Object类型的数组elementData,因此可用于存储任意类型的数据。我们知道,数组是长度恒定的。而ArrayList相当于是一个长度可变的动态数组,一起来看看的它的扩容机制

扩容机制

ArrayList是一个底层基于数组实现的集合容器。当我们在创建ArrayList对象时,默认数组长度为10,当然也可以在创建时指定长度。之后在程序执行过程中,不断地向ArrayList中添加数据。当数据存储达到底层数组最大容量时则会触发扩容机制

扩容原理

首先创建一个新的数组,新数组的长度时原数组的1.5倍。然后调用Arrays.copyOf()方法将原数组的所有数据copy到新数组中,再将当前新添加的数据添加至新数组并返回

源码分析

先来看ArrayList类生命的几个参数

// 默认ArrayList底层数组长度为10
private static final int DEFAULT_CAPACITY = 10;
// 空数组  其他地方调用
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认长度的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// elementData 真正存储数据的数组  ArrayList的容量就是该数组的长度 
// 默认创建空的ArrayList将在第一次调用add方法时将该数组扩容成为DEFAULT_CAPACITY = 10的容量
transient Object[] elementData;
// size代表的是当前elementData数组中存储的元素个数  并不是数组的容量! 
private int size;

当我们创建ArrayList对象并且指定长度时调用的构造器

ArrayList<> list = new ArrayList<>(12);
public ArrayList(int initialCapacity) {
    // initialCapacity就是你指定的长度
    // 逻辑判断
        if (initialCapacity > 0) {
// initialCapacity符合要求就创建新数组 长度为你指定的长度
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
// 如果initialCapacity为0则得到一个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }

不指定长度时构造器

public ArrayList() {
    // 使用默认长度大小的数组
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

需要注意的是,我们不论用上述哪个构造器,首先第一步创建出来的都是空数组,但是会在第一次添加数据的时候执行不同方法进行扩容

下面我们从调用add()方法开始分析:

每次添加数据都会将size+1去进行判断,保证数组拥有至少存储这个新数据的空间

public boolean add(E e) {
    // 就此处开始一系列的扩容判断和操作
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;// 添加数据
        return true;
    }
// 下面是add方法中第一行执行的方法  此处minCapacity就是数组的最小容量  也就是当前存储的元素个数+1
private void ensureCapacityInternal(int minCapacity) {
 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

CalculateCapacity()方法用于判断当前数组是否是默认容量的数组,如果是默认容量的数组那么此时就要确定是否是第一次添加数据

如果是第一次添加数据,那么就将返回DEFAULT_CAPACITY=10也就是minCapacity=10

如果不是第一次添加数据,就将添加新数据之后的最小容量返回

其次还要注意的是,如果在创建ArrayList对象时使用的指定长度的构造器那么就会直接返回最小容量,也就是说如果你创建ArrayList指定长度为0,那么此时就会返回minCapacity=1,指定长度为0这个清空后面给到具体分析

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

ensureExplicitCapacity()方法用于判断是否需要扩容,经过上述方法将最小容量minCapacity传入ensureExplicitCapacity方法,如果这个所需最小容量大于当前数组容量(也就是当前数组存不下这个新数据了)则触发扩容机制grow()方法,反之不会进行扩容

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;// modCount++是做什么的? 我也不知道
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

在分析grow()方法之前我们先明确两个概念:

ArrayList定义了允许存储的最大容量也就是Integer允许的范围-8,如果溢出则是负数

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

Integer类型的范围是: -2的31次方~2的31次方减1

@Native public static final int MAX_VALUE = 0x7fffffff;

下面我们来看真正实现扩容的grow()方法

private void grow(int minCapacity) {
        // overflow-conscious code
    // 获取原数组容量
        int oldCapacity = elementData.length;
    // 新数组的容量是原数组容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 判断新数组容量是否小于最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
    // 判断新容量是否超过了ArrayList允许的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
    // 调用copyOf方法将原数据全部复制到新的空数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
// 超出了ArrayList容量执行hugeCapacity
    private static int hugeCapacity(int minCapacity) {
        // 因为Integer溢出则为负数,此处判断是否溢出
        if (minCapacity < 0) // overflow
            // 抛出异常  内存溢出
            throw new OutOfMemoryError();
        // 没有溢出则判断最小容量是否大于了ArrayList允许的最大值
        // 大于--> 返回Integer最大值
        // 小于--> 返回ArrayList允许的最大值
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

值得注意的是,如果当前数组是无参构造器默认生成的空数组,并且是第一次添加数据时,那么数组的长度将会直接变为10

如果当前的数组是有参构造器(指定长度)生成并且指定初始容量为0,那么在前四次调用add方法添加数据时每次扩容都是+1,只有在第五次才会执行1.5倍扩容。

因为第一次添加则是minCapacity=1,oldCapacity=0 执行int newCapacity = oldCapacity + (oldCapacity >> 1);之后newCapacity 还是0,则执行if (newCapacity - minCapacity < 0) -->newCapacity = minCapacity;也就是1,那么newCapacity 就为1,后面的三次以此类推差不多

那么至此,ArrayList就完成了扩容

到此这篇关于Java ArrayList扩容机制原理深入分析的文章就介绍到这了,更多相关Java ArrayList扩容机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring boot 运用策略模式实现避免多次使用if

    Spring boot 运用策略模式实现避免多次使用if

    这篇文章主要介绍了Spring boot 运用策略模式实现避免多次使用if,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • SpringBoot实现异步事件Event详解

    SpringBoot实现异步事件Event详解

    这篇文章主要介绍了SpringBoot实现异步事件Event详解,异步事件的模式,通常将一些非主要的业务放在监听器中执行,因为监听器中存在失败的风险,所以使用的时候需要注意,需要的朋友可以参考下
    2023-11-11
  • 一文搞懂Java的SPI机制(推荐)

    一文搞懂Java的SPI机制(推荐)

    Java定义了一套JDBC的接口,但并未提供具体实现类,而是在不同云厂商提供的数据库实现包。这篇文章给大家介绍Java的SPI机制,感兴趣的朋友一起看看吧
    2021-11-11
  • Java中Velocity快速对变量中的引号特殊字符进行转义

    Java中Velocity快速对变量中的引号特殊字符进行转义

    Velocity是一个基于Java的模板引擎,与Freemarker类似,这篇文章主要介绍了Java中Velocity如何对变量中的引号特殊字符进行转义,主要记录一下在使用中碰到的要对引号特殊字符进行转义的问题,需要的朋友可以参考下
    2023-07-07
  • servlet的常见注册方式总结

    servlet的常见注册方式总结

    servlet大家都不陌生,当开发 Web 应用程序时,注册 Servlet 是一个常见的任务,本文将介绍一些常见的 Servlet 注册方法,希望对大家有所帮助
    2023-10-10
  • 使用java springboot设计实现的图书管理系统(建议收藏)

    使用java springboot设计实现的图书管理系统(建议收藏)

    这篇文章主要介绍了使用java springboot设计实现的图书管理系统,包含了整个的开发过程,以及过程中遇到的问题和解决方法,对大家的学习和工作具有借鉴意义,建议收藏一下
    2021-08-08
  • Java面试题冲刺第十八天--Spring框架3

    Java面试题冲刺第十八天--Spring框架3

    这篇文章主要为大家分享了最有价值的三道关于Spring框架的面试题,涵盖内容全面,包括数据结构和算法相关的题目、经典面试编程题等,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • springboot配置数据库密码特殊字符报错的解决

    springboot配置数据库密码特殊字符报错的解决

    这篇文章主要介绍了springboot配置数据库密码特殊字符报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Spring声明式事务@Transactional注解实现元数据驱动的事务管理

    Spring声明式事务@Transactional注解实现元数据驱动的事务管理

    这篇文章主要为大家介绍了Spring声明式事务@Transactional注解实现元数据驱动的事务管理示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • IDEA中创建maven项目webapp目录无法识别即未被标识的解决办法

    IDEA中创建maven项目webapp目录无法识别即未被标识的解决办法

    在学习SpringMVC课程中,基于IDEA新建maven项目模块后,webapp目录未被标识,即没有小蓝点的图标显示,所以本文给大家介绍了IDEA中创建maven项目webapp目录无法识别即未被标识的解决办法,需要的朋友可以参考下
    2024-03-03

最新评论