ArrayList扩容机制(基于jdk1.8)

ArrayList扩容机制(基于jdk1.8)一.ArrayList继承了AbstractList,实现了List接口,底层实现基于数组,因此可以认为是一个可变长度的数组。二.在讲扩容机制之前,我们需要了解一下ArrayList中最主要的几个变量://定义一个空数组以供使用privatestaticfinalObject[]EMPTY_ELEMENTDATA={};//也是一个空数组,跟上边的空数组不同之处在于,这个是在默…

大家好,又见面了,我是你们的朋友全栈君。

一.ArrayList继承了AbstractList,实现了List接口,底层实现基于数组,因此可以认为是一个可变长度的数组。
二.在讲扩容机制之前,我们需要了解一下ArrayList中最主要的几个变量:

//定义一个空数组以供使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断,后面会讲到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数组中的元素,注意此变量是transient修饰的,不参与序列化
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length,后边会说到
private int size;

三.ArrayList有三个构造函数,不同的构造函数会影响后边的扩容机制判断
1.默认的无参构造

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

可以看到,调用此构造函数,返回了一个空的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此数组长度为0.
2.给定初始容量的构造函数

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);
    }
}

逻辑很简单,就是构造一个具有指定长度的空数组,当initialCapacity为0时,返回EMPTY_ELEMENTDATA
3.包含特定集合元素的构造函数

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;
    }
}

把传入的集合转换为数组,然后通过Arrays.copyOf方法把集合中的元素拷贝到elementData中。同样,若传入的集合长度为0,返回EMPTY_ELEMENTDATA
四.扩容机制
扩容开始于集合添加元素方法,添加元素有两种方法

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

   ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}

可以看到两个方法都调用了ensureCapacityInternal(size + 1)方法,把数组长度加1以确保能存下下一个数据

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

此方法会先调用calculateCapacity方法,此时minCapacity为1,即size+1,因为初始时size为0

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

重点来了,此方法会判断当前数组是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,之前就强调了无参构造时才会返回这个数组。所以,若创建ArrayList时调用的是无参构造,此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity的最大值,因此,最终会返回固定值10;若创建ArrayList时调用了有参构造,则此方法会返回1,注意这个
minCapacity变量只是第一次调用add方法时值为1,此后的调用需要根据实际的数组长度size+1。
然后调用ensureExplicitCapacity方法,

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

modCount++用到了快速失败机制,此处先不做讨论。
如果minCapacity大于elementData.length,则会调用grow方法,注意,这个elementData.length返回的是当前数组的容量,而不是数组实际的长度size。如果调用了有参构造,例如传入的容量为5,则此时elementData.length值即为5,而此时第一次调用add时,size值为0,因此minCapacity为1,不满足条件,此情况不需要扩容调用grow方法;如果调用了无参构造返回数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,注意这个数组只是一个空数组,因此此时elementData.length为0,满足条件,需要扩容调用grow方法。
可能说的太啰嗦,通俗来讲,就是如果ArrayList给定了特定初始容量,则此处需要根据实际情况确定是否调用grow方法,即有可能不需要扩容。如果没有指定初始容量,第一次调用add则此处一定需要调用grow方法。
那么,下面就看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);
}

int newCapacity = oldCapacity + (oldCapacity >> 1)此行代码即为扩容的核心,oldCapacity为原来的容量,右移一位,即除以2,因此这句的意思就是新的容量newCapacity=oldCapacity+oldCapacity /2,即原来的1.5倍。
然后判断newCapacity如果小于传入的minCapacity,则直接让newCapacity等于minCapacity,即不需要扩容计算(当无参构造时,elementData.length为0,所以oldCapacity也为0,minCapacity为10,因此最终newCapacity为10)。
然后判断newCapacity是否大于设定的MAX_ARRAY_SIZE,此处
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8;
如果大于,则调用hugeCapacity方法

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值,否则返回MAX_ARRAY_SIZE
最后,通过Arrays.copyOf方法把原数组的内容放到更大容量的数组里面。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/130425.html原文链接:https://javaforall.net

(0)
上一篇 2022年6月14日 上午11:46
下一篇 2022年6月14日 上午11:46


相关推荐

  • Text Justification — LeetCode

    Text Justification — LeetCode

    2022年1月7日
    44
  • ant打包war时报错:java.lang.IllegalArgumentException: MALFORMED

    ant打包war时报错:java.lang.IllegalArgumentException: MALFORMED在配置 jenkins 的自动发布时发现有些工程打包成 war 再发布到 tomcat 的时候会出现如下错误 java lang IllegalArgum MALFORMED 找了好久最终发现是因为 war 目录中存在中文文件名 应该是因为打包 war 时编码的问题导致打包后的 war 内中文文件名无法正确读取 最后将所有中文文件名修改之后重新打包问题解决

    2026年3月16日
    2
  • WDA原理分析

    WDA原理分析1、什么是WDAWebDriverAgent是Facebook在17年的SeleniumConf大会上推出了一款新的iOS移动测试框架。下面摘录一段官方对于WebDriverAgent的介绍字段:(官方文档:https://github.com/facebook/WebDriverAgent)WebDriverAgent在iOS端实现…

    2022年7月12日
    20
  • 微服务治理(Nocas)

    微服务治理(Nocas)实现微服务治理 Nocas 1 服务治理介绍实现微服务之间的调用 我们可以通过 HTTP 客户端工具完成 但是我们需要把服务提供者的网络地址 IP 端口 等硬编码到代码中 这样会存在以下问题 一旦服务提供者地址变化 就需要手工修改代码一旦是多个服务提供者 无法实现负载均衡功能一旦服务变得越来越多 人工维护调用关系困难那么应该怎么解决呢 这时候就需要通过注册中心动态的实现服务治理 什么是服务治理服务治理是微服务架构中最核心最基本的模块 用于实现各个微服务的自动化注册与发现 服务注册

    2026年3月16日
    3
  • python读写json文件

    python读写json文件python 中和 json 读写相关的主要是 json 模块的以下四个函数 dumps 将一个 python 对象编码为 json 对象 loads 讲一个 json 对象解析为 python 对象 dump 将 python 对象写入文件 load 从文件中读取 json 数据 1 dumps 和 loads 主要用于 Python 和 json 对象的相互转化 importjsonpr

    2026年3月18日
    2
  • get 请求和post请求的区别

    get 请求和post请求的区别HTTP 的 Get Post 请求区别博客分类 nbsp WEB J2EE 开发框架 IIS 应用服务器浏览器 MVCASP nbsp HTTP 的 Get Post 请求区别归纳 1 get 是从服务器上获取数据 post 是向服务器传送数据 g nbsp et 和 post 只是一种传递数据的方式 get 也可以把数据传到服务器 他们的本质都是发送请求和接收结果 只是组织格式和数据量上面有差别 ht

    2025年9月22日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号