如何正确扩容ArrayList?我当前的实现是否为最优方式?
嘿,这个坑我之前写归并排序的时候也踩过,太懂那种突然报越界错误的迷惑感了!
首先得把核心问题说透:你用new ArrayList<>(size)初始化的时候,这里的size是ArrayList的初始容量(capacity),不是它实际存储的元素数量(size)。这时候你的tempArray其实是空的(size=0),调用set(index, ...)自然会抛出IndexOutOfBoundsException——因为set要求索引必须小于当前的元素数量,而不是容量。
你说的“循环执行size次操作来增加数组的size”(比如每次add一个null或默认值),虽然能解决问题,但绝对不是最优方式。下面给你几个更高效简洁的方案:
方案1:用Collections.nCopies直接初始化指定size的ArrayList
可以用Collections.nCopies创建一个包含size个null元素的不可变列表,再传入ArrayList的构造方法:
ArrayList<T> tempArray = new ArrayList<>(Collections.nCopies(size, null));
这样初始化后的tempArray的元素数量直接就是你需要的size,底层数组容量也刚好匹配,之后可以直接用set修改任意索引位置的元素,完全不会有越界问题。这种方式比循环add高效得多,因为它是一次性把所有元素复制到ArrayList的底层数组,不需要循环执行size次add操作。
方案2:直接用泛型数组代替ArrayList
其实归并排序里的临时数组,用原生数组反而更合适——数组访问速度更快,也不会有容量和元素数量混淆的问题。你可以这样创建泛型数组:
T[] tempArray = (T[]) new Object[size];
虽然这里会有类型转换的警告,但这是安全的(我们明确知道这个数组只会存储T类型元素)。之后直接通过tempArray[index] = ...赋值就行,完全不用考虑元素数量的问题,数组长度就是你需要的size。
为什么循环add不是最佳方式?
如果用循环for (int i = 0; i < size; i++) tempArray.add(null);,虽然能把元素数量提升到目标值,但:
- 代码不够简洁,多了不必要的循环逻辑
- 哪怕初始容量设为size不会触发扩容,循环执行size次add还是会有额外开销(比如每次add都要检查容量、更新元素数量),不如一次性初始化高效
所以综合来看,方案1或方案2都比循环add要好得多:如果之后需要用到ArrayList的其他方法(比如动态增删)就选方案1;如果只是做临时存储和索引访问,方案2的数组更高效。
内容的提问来源于stack exchange,提问作者Alex

