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。

目录
相关文章
|
4天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
8天前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
11天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
8天前
|
Java 数据库连接 开发者
Java中的异常处理机制及其最佳实践####
在本文中,我们将探讨Java编程语言中的异常处理机制。通过深入分析try-catch语句、throws关键字以及自定义异常的创建与使用,我们旨在揭示如何有效地管理和响应程序运行中的错误和异常情况。此外,本文还将讨论一些最佳实践,以帮助开发者编写更加健壮和易于维护的代码。 ####
|
15天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
14天前
|
安全 IDE Java
Java反射Reflect机制详解
Java反射(Reflection)机制是Java语言的重要特性之一,允许程序在运行时动态地获取类的信息,并对类进行操作,如创建实例、调用方法、访问字段等。反射机制极大地提高了Java程序的灵活性和动态性,但也带来了性能和安全方面的挑战。本文将详细介绍Java反射机制的基本概念、常用操作、应用场景以及其优缺点。 ## 基本概念 ### 什么是反射 反射是一种在程序运行时动态获取类的信息,并对类进行操作的机制。通过反射,程序可以在运行时获得类的字段、方法、构造函数等信息,并可以动态调用方法、创建实例和访问字段。 ### 反射的核心类 Java反射机制主要由以下几个类和接口组成,这些类
32 2
|
18天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 3
|
15天前
|
Java 开发者
深入理解Java异常处理机制
【10月更文挑战第29天】在Java的世界中,异常处理如同生活的调味品,不可或缺。它确保了程序在遇到错误时不会崩溃,而是优雅地继续运行或者给出提示。本文将带你领略异常处理的奥秘,从基础的try-catch语句到高级的自定义异常,让你在面对程序中的各种“意外”时,能够从容应对。
|
17天前
|
SQL Java
探索Java中的异常处理机制
【10月更文挑战第26天】 在本文中,我们将深入探讨Java编程语言的异常处理机制。通过分析不同类型的异常、异常的捕获与抛出方式,以及如何自定义异常类,读者将能够更好地理解并应用Java中的异常处理机制来提高代码的健壮性和可读性。
23 0
|
9天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。