Java 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扩容机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!