共计 2970 个字符,预计需要花费 8 分钟才能阅读完成。
在 Java 编程中,ArrayList 是一种常用的数据结构,它提供了便捷的动态数组功能。然而,了解 ArrayList 的内部机制对于优化代码性能和避免不必要的资源浪费至关重要。本文将深入探讨 ArrayList 的两个关键问题:初始容量和扩容机制。我们将揭示 ArrayList 的初始容量到底是 0 还是 10,并详细解析 ArrayList 的扩容机制,包括何时触发扩容、扩容的策略以及如何提高代码的效率和性能。通过对 ArrayList 的深入了解,我们能够更好地理解和利用这一重要的数据结构,为我们的 Java 编程提供更强大的工具。
ArrayList 的初始容量
ArrayList 的初始容量是指创建一个空的 ArrayList 时,它内部数组的大小。这个大小会影响到 ArrayList 的内存占用和扩容次数。不同的版本的 Java 实现可能有不同的初始容量设置,但通常有两种方式来指定或修改它:
- 使用无参构造函数创建一个默认的 ArrayList,它的初始容量由实现决定。例如,在 Java 1.6 中,它的初始容量为 10,而在 Java 1.7 和 1.8 中,它的初始容量为 0,只有在添加第一个元素时才分配 10 个对象空间。
源码如下:
// 默认容量大小
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认容量的数组对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储元素的数组
transient Object[] elementData;
// 数组中元素个数,默认是 0
private int size;
// 无参初始化,默认是空数组
public ArrayList() {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);
}
}
- 使用带有 int 参数的构造函数创建一个指定初始容量的 ArrayList,这样可以根据预期的元素数量来优化内存分配和扩容次数。例如,如果我们知道要存储 100 个元素,我们可以这样创建一个 ArrayList:
ArrayList list = new ArrayList(100);
这样就可以避免多次扩容的开销,同时也不会浪费太多的空间。
ArrayList 的扩容机制
ArrayList 的扩容机制是指当 ArrayList 的内部数组已经满了,无法再容纳新的元素时,它会自动创建一个更大的数组,并将原来的数组的内容复制到新的数组中,以实现动态增长的功能。这个过程会影响到 ArrayList 的性能和内存使用,因为数组的创建和复制都是耗时的操作,而且会产生额外的垃圾对象。
ArrayList 的扩容策略也可能因为不同的 Java 实现而有所差异,但通常有以下几个特点:
- 扩容的时机是在添加元素之前,检查当前的容量是否足够,如果不够,就进行扩容。例如,在 Java 1.8 中,ArrayList 的 add() 方法的源码如下:
// 添加元素
public boolean add(E e) {
// 确保数组容量够用,size 是元素个数
ensureCapacityInternal(size + 1);
// 直接在下个位置赋值
elementData[size++] = e;
return true;
}
// 确保数组容量够用
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算所需最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果数组等于空数组,就设置默认容量为 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 确保容量够用
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果所需最小容量大于数组长度,就进行扩容
if (minCapacity - elementData.length> 0)
grow(minCapacity);
}
其中,ensureCapacityInternal 方法会判断是否需要扩容,如果需要,就调用 grow 方法进行扩容。
- 扩容的幅度是按照一定的比例增加,而不是固定的值,这样可以保证扩容的次数是对数级别的,而不是线性级别的,从而降低平均的时间复杂度。例如,在 Java 1.8 中,ArrayList 的 grow 方法的源码如下:
// 扩容,就是把旧数据拷贝到新数组里面
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 计算新数组的容量大小,是旧容量的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity>> 1);
// 如果扩容后的容量小于最小容量,扩容后的容量就等于最小容量
if (newCapacity - minCapacity 0)
newCapacity = hugeCapacity(minCapacity);
// 扩容并赋值给原数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
总结
ArrayList 是一种灵活和高效的动态列表,它可以根据需要自动调整容量,以存储任意数量的元素。但是,它的初始容量和扩容机制也会影响到它的性能和内存使用,因此,在使用 ArrayList 时,我们应该根据实际的场景和需求,合理地选择或指定它的初始容量,以避免不必要的开销和浪费。
如果你对 Java 工程师职业和编程技术感兴趣,不妨访问编程狮官网(https://www.w3cschool.cn/)。编程狮官网提供了大量的技术文章、编程教程和资源,涵盖了 Java 工程师、编程、职业规划等多个领域的知识。无论你是初学者还是有经验的开发者,编程狮官网都为你提供了有用的信息和资源,助你在编程领域取得成功。不要错过这个宝贵的学习机会!
原文地址: 揭秘 ArrayList 初始容量与扩容机制——90% 的人都不知道