java源码分析:ArrayList的扩容机制

简介: ArrayList是java中最常用的集合之一,底层的数据结构是数组,在学习集合时,阅读源码是必不可少的环节之一,阅读源码可以有效的帮助我们深入了解其工作原理,下面根据源码详细的介绍下扩容机制,环境为jdk1.8。

ArrayList是java中最常用的集合之一,底层的数据结构是数组,在学习集合时,阅读源码是必不可少的环节之一,阅读源码可以有效的帮助我们深入了解其工作原理,下面根据源码详细的介绍下扩容机制,环境为jdk1.8。


首先看看它的无参构造,ArrayList无参构造方法如下:


  /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }


先来看看elementData,这是一个Object数组,也就是集合中的元素,其定义如下:


  /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
  transient Object[] elementData; // non-private to simplify nested class access


DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个final的Object空数组,定义如下:


  /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


由注释我们可知这是一个共享的空数组,用于默认大小的空实例。


那么ArrayList什么时候开始扩容呢,从add()方法中可以找到答案,源码如下:


  /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
      // 检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


很明显第一句代码就是其扩容检查方法,参数是当前的集合大小+1,也就是新增元素后的数组实际大小。继续跟进ensureCapacityInternal()方法:


  private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }


此处的minCapacity就是需要的最少空间。继续跟进calculateCapacity()方法代码:


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


由此可以知道:如果集合中的元素是默认的空数组的话,那么将会返回DEFAULT_CAPACITY与minCapacity两者的较大值。DEFAULT_CAPACITY定义如下:


  /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;


所以默认的初始容量是10。再进到ensureExplicitCapacity()方法中来看:


  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        // 如果需要的最少空间大于目前的数组长度,则需要扩容
        if (minCapacity - elementData.length > 0)
          // 扩容方法
            grow(minCapacity);
    }


这里的modCount是指数组结构被修改的次数,从代码中可以看到如果需要的最小空间大于当前集合的长度的话就会调用grow()方法,方法定义如下:


  /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 右移等于除2,也就是说新容量等于旧容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 这里会判断扩容后的容量是否会溢出
        // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        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);
    }


此处的grow()方法就是最终的扩容方法,if (newCapacity - MAX_ARRAY_SIZE > 0)这个判断就很有意思了,可能有人会想为什么不写成if (newCapacity > MAX_ARRAY_SIZE)呢?他们表达的是同一个意思吗?答案:不是。因为如果扩容后newCapacity溢出的话,那么它就会变成负数,前者的判断就会返回true,后者则会返回false,两者相反。

再继续看如果溢出会怎么样:


  private static int hugeCapacity(int minCapacity) {
    // 小于0说明是负数,所以判断为溢出,此时抛出内存溢出错误
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }


由源代码可知:如果需要的最小空间大于最大的数组空间的话(前面说过最大数组空间)那么新的数组大小就是Integer的最大值,否则为最大数组空间。


总结:


当每次添加新元素时,ArrayList都会检查是否需要扩容,如需扩容,会扩容成当前容量的1.5倍,如果溢出(负数)的话则会抛出OutOfMemoryError,如果介于Integer.MAX_VALUE和MAX_ARRAY_SIZE两者之间,则取Integer.MAX_VALUE。

目录
相关文章
|
1月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
87 4
|
2月前
|
缓存 Java 开发者
Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
188 3
|
4月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
243 4
|
4月前
|
Java 索引
Java ArrayList中的常见删除操作及方法详解。
通过这些方法,Java `ArrayList` 提供了灵活而强大的操作来处理元素的移除,这些方法能够满足不同场景下的需求。
506 30
|
6月前
|
人工智能 安全 JavaScript
Java ArrayList:动态数组
本文探讨Java中的数组,对比C/C++、JS/PHP/Python等语言的数组特性。文章分析了Java数组的定义、创建方式及其规范,指出其优缺点。Java数组作为引用类型,在堆上分配内存,支持动态大小,避免了C/C++中裸数组的常见问题(如越界访问)。然而,Java数组也存在性能瓶颈和设计缺陷,例如运行时的安全检查影响速度,无法创建超大数组或泛型数组,且多线程场景下缺乏同步机制。作者建议在实际开发中用集合替代数组以规避这些问题。
172 1
|
6月前
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
180 0
|
6月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
242 0
|
6月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
380 32
AQS:Java 中悲观锁的底层实现机制
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
143 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
160 1
下一篇
oss云网关配置