首页 > 其他分享 >ArrayList动态扩容

ArrayList动态扩容

时间:2023-01-30 10:44:34浏览次数:59  
标签:扩容 容量 ArrayList elementData add minCapacity 数组 动态

一、 ArrayList 的动态扩容机制

要了解其动态扩容机制就必须先看它的源码,源码是基于 jdk 1.8 的

1. ArrayList 的主要属性

// 如果不指定容量(空构造器),则在添加数据时的空构造器默认初始容量最小为 10
private static final int DEFAULT_CAPACITY = 10;

// 出现在需要用到空数组的地方,其中一处是使用自定义初始容量构造方法时候如果你指定初始容量为0的时候,那么elementData指向该数组
// 另一处是使用包含指定collection集合元素的列表的构造方法时,如果被包含的列表中没有数据,那么elementData指向该数组
private static final Object[] EMPTY_ELEMENTDATA = {};

// 如果使用默认构造方法,那么elementData指向该数组
// 在添加元素时会判断是否是使用默认构造器第一次添加,如果是数组就会扩容至10个容量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 默认未初始化的储存 ArrayList集合元素的底层数组,其长度就是 ArrayList的容量
transient Object[] elementData;

// 私有的elementData数组中具体的元素对象的数量,可通过size方法获得。默认初始值为0,在add、remove等方法时size会改变
private int size;

可以看到 ArrayList 底层核心是一个 Object[] elementData 的数组

2. ArrayList 的构造器

// 默认的构造器
public ArrayList() {
	// Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} 空数组
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 自定义容量大小的构造器
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 的默认构造器时,它的初始容量就是 0
  • 如果使用 ArrayList 的有参构造器时,它的初始容量就是你传入的参数 initialCapacity的值

3. ArrayList 的动态扩容

根据上面的两个结论,不管是使用默认或有参构造器时,我们可以使其初始容量为 0,那么它的动态扩容发生在哪里?可以肯定就发生在 add() 方法中,源码如下:

public boolean add(E e) {
	// ensureCapacityInternal() 如下
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
    return true;
}
  • 按照我们第一次 add, size 肯定是 0 了,所以 ensureCapacityInternal 这个函数传入的是 1
// 第一次 add 时,参数 minCapacity = 1
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// calculateCapacity() 方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {	
	// 如果是第一次 add 元素
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		// minCapacity 设置为 DEFAULT_CAPACITY 与 minCapacity 的最大值
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
    return minCapacity;
}

// ensureExplicitCapacity() 方法
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

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

elementData.length 是数组长度,它是随着数组扩容而动态变化的

  • 如果是第一次 add 元素时,它为 0
  • 之后它是随着 oldCapacity + (oldCapacity >> 1) 而动态变化的

首先看 calculateCapacity() 方法

  • 如果是第一次 add 元素,那么 minCapacity 设置为 DEFAULT_CAPACITY 与 minCapacity 的最大值,即 10 与 1 取最大值,这里返回最大值 10
  • 否则,直接返回 minCapacity

再看 ensureExplicitCapacity() 方法

  • 如果是第一次 add 元素,由上面方法可知 minCapacity = 10,即 10 - 0 > 0 返回 true,再调用 grow() 方法
  • 只要 minCapacity - elementData.length > 0 为 true,就会再调用 grow() 方法
private void grow(int minCapacity) {
    // 原容量
    int oldCapacity = elementData.length;
    // 扩容后的容量,扩大到原容量的 1.5 倍左右,右移一位相当于原数值除以 2 的商
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新容量减去最小容量的值小于 0
    if (newCapacity - minCapacity < 0)
        // 新容量等于最小容量
        newCapacity = minCapacity;
    // 如果新容量减去建议最大容量的值大于 0
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 调整新容量上限或者抛出 OutOfMemoryError
        newCapacity = hugeCapacity(minCapacity);
        
     // 最终进行新数组的构建和重新赋值,此后原数组被摒弃
     // elementData:原数组; newCapacity:新数组容量
    elementData = Arrays.copyOf(elementData, newCapacity);
}

elementData.length 是数组长度,它是随着数组拷贝而动态变化的

  • 如果是第一次 add 元素时,它为 0
  • 之后它是随着 oldCapacity + (oldCapacity >> 1) 而动态变化的

如果是第一次 add 元素,由上面的方法可知参数 minCapacity = 10,第一次 add 元素 oldCapacity = 0,可得知 newCapacity = 0 + 0,由于 newCapacity - minCapacity < 0,所以 newCapacity = minCapacity = 10 重新赋值,最终进行新数组的构建和拷贝赋值

2. 小结

1. 初始容量

如果使用 ArrayList 的默认无参构造器时,它的初始容量就是 0

如果使用 ArrayList 的有参构造器时,它的初始容量就是你传入的参数 initialCapacity的值

2. 动态扩容大小

  • ArrayList 的初始容量为 0,当第一次 add 元素时,才会发生扩容操作,它的扩容大小是 10

  • 当第一次 add 元素完成后,此时的 elementData.length = 10,后面要想发生扩容操作,必须 minCapacity - elementData.length > 0 为 true,即 minCapacity 最小为 11,也就是说当 ArrayList 第11次 add 时,才会接着发生扩容操作,且扩容后大小约为原大小的1.5倍。

版权声明:本文为CSDN博主「桐花思雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:ArrayList的动态扩容机制

标签:扩容,容量,ArrayList,elementData,add,minCapacity,数组,动态
From: https://www.cnblogs.com/Fogram-c/p/17074771.html

相关文章

  • 春哥博客 - ArrayList集合对象
    1、ArrayList集合对象usingSystem;usingSystem.Collections;namespaceArrayList集合{classProgram{staticvoidMain(string[]args)......
  • Linux 标准分区扩容
    0x00 前提安装growpart工具yuminstall-ycloud-utils-growpartyuminstallxfsprogs0x01使用growpart扩容工具扩容growpart/dev/sda1#注意磁盘和序号之间......
  • 日本国的情报收集确实不一般 —— 日本国立研究机关对世界各国科技发展动态及新闻的收
    最近在做国家项目需要收集日本的科技发展情况,搜着搜着发现原来日本国也在时刻不停的收集中国的科技发展动态及新闻,这么一细看就不得不说日本国在情报收集上确实是下了一番......
  • Redis的设计与实现(1)-SDS简单动态字符串
    现在在高铁上,赶着春节回家过年,无座站票,电脑只能放行李架上,面对着行李架撸键盘--看过<Redis的设计与实现>这本书,突然想起,便整理下SDS的内容,相对后面的章节,......
  • elemenplus form表单的动态配置写法
    elementplusform的动态配置写法模板代码部分<template> <divclass="cardcontent-box"> <el-alerttitle="通过component:is组件属性&&v-bind属性透传,可以将......
  • 流浪地球动态桌面壁纸【高清】
    壁纸是动态的,因为分辨率问题录下来的视频会卡顿,所以我就不上传视频了壁纸与工具以及全部打包上传网盘了,需要自取链接:https://pan.baidu.com/s/1hAJWxTHleINhG7PjF_5u2......
  • 【ASP.NET Core】动态映射MVC路由
    ASP.NETCore中的几大功能模块(RazorPages、MVC、SignalR/Blazor、Mini-API等等)都以终结点(EndPoint)的方式公开。在HTTP管道上调用时,其扩展方法基本是以Map开头,如 Map......
  • 线程安全集合CopyOnWriteArrayList
    解决多线程的集合有以下几种1、Vertor(所有方法上加synchronized锁)能保证多线程安全,数据一致,但性能低下一般不用2、Collections.synchronizedList方法返回的List 在方......
  • Fibonacci数列,从递归,O(N)迭代,动态规划,O(logN)矩阵快速幂到O(1)通项公式
    题目链接:剑指Offer10-I.斐波那契数列-力扣(LeetCode)朴素递归做法核心是一个递归边界和递归体,复杂度分析可画递归树可得,时间复杂度是O(2N),这是一个估算的上界,递归树......
  • ubuntu系统lvm扩容根分区
    需要对其根分区扩容给虚拟机增加内存后不能立马使用,需要对磁盘进行重新分配,采用的是lvm方式。虚拟机扩容到40G,然后开始扩容前提:需要扩容的分区,必须是lvm的。需要新建分区,......