1. 概述
Java允许我们创建固定大小的数组或使用集合类来完成类似的工作。
在本教程中,我们将了解ArrayList的容量与Array的大小之间的差异。
我们还将查看何时应使用容量初始化ArrayList的示例,以及在内存使用方面的优缺点。
2. 例子
要了解差异,让我们首先尝试这两种选择。
2.1 数组的大小
在Java中,在创建数组的新实例时必须指定数组的大小:
Integer[] array = new Integer[100];
System.out.println("Size of an array:" + array.length);
在这里,我们创建了一个大小为100的整数数组,结果如下:
Size of an array:100
2.2 ArrayList的容量
从技术上讲,新创建的ArrayList的默认容量(DEFAULT_CAPACITY)是10。但是,Java 8出于性能原因更改了此初始容量的使用方式。
它不会立即使用,并且一旦将新元素添加到列表中就会延迟使用。
因此,空ArrayList的默认容量在Java 8中是0而不是10。一旦添加了第一个元素,就会使用DEFAULT_CAPACITY,即10。
由于没有获取默认容量的内置方法,让我们使用反射来返回它:
public static int getDefaultCapacity(ArrayList<?> arrayList) throws Exception {
if (arrayList == null) {
return 0;
}
Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
return ((Object[]) field.get(arrayList)).length;
}
现在,让我们确认空ArrayList的默认容量为0:
@Test
void givenEmptyArrayList_whenGetDefaultCapacity_thenReturnZero() throws Exception {
ArrayList<Integer> myList = new ArrayList<>();
int defaultCapacity = DefaultArrayListCapacity.getDefaultCapacity(myList);
assertEquals(0, defaultCapacity);
}
接下来,让我们看看将新元素添加到空ArrayList时会发生什么:
@Test
void givenEmptyArrayList_whenAddItemAndGetDefaultCapacity_thenReturn10() throws Exception {
ArrayList<String> myList = new ArrayList<>();
myList.add("ITEM 1");
int defaultCapacity = DefaultArrayListCapacity.getDefaultCapacity(myList);
assertEquals(10, defaultCapacity);
}
Java 8中这一变化的目的是节省内存消耗,避免立即分配内存。
现在,让我们创建一个初始容量为100的ArrayList:
List<Integer> list = new ArrayList<>(100);
assertEquals(0, list.size());
由于尚未添加任何元素,因此大小为0。
现在,让我们向列表中添加一个元素并检查其大小:
list.add(10);
assertEquals(10, list.size());
3. 数组中的大小与ArrayList
下面是数组大小和ArrayList容量之间的一些主要区别。
3.1 修改大小
数组是固定大小的。一旦我们用一些int值作为其大小初始化数组,它就不能改变。大小和容量也彼此相等。
ArrayList的大小和容量不固定。列表的逻辑大小根据其中元素的插入和删除而变化。这是与其物理存储大小分开管理的。同样,当达到ArrayList容量的阈值时,它会增加容量以为更多元素腾出空间。
3.2 内存分配
数组内存在创建时分配。当我们初始化数组时,它会根据数组的大小和类型分配内存。它使用引用类型的null值和基本类型的默认值初始化所有元素。
ArrayList随着它的增长改变内存分配。当我们在初始化ArrayList时指定容量时,它会分配足够的内存来存储不超过该容量的对象。逻辑大小保持为0。当需要扩展容量时,将创建一个新的更大的数组,并将值复制到其中。
我们应该注意到,空ArrayList对象有一个特殊的0大小的单例数组,这使得创建它们的成本非常低。还值得注意的是,ArrayList在内部使用了一个Object引用数组。
4. 何时使用容量初始化ArrayList
当我们在创建ArrayList之前知道它所需的大小时,我们可能希望初始化它的容量,但这通常不是必需的。但是,这可能是最佳选择的原因有几个。
4.1 构建大型ArrayList
当我们知道列表会变大时,最好用初始容量初始化一个列表。这可以防止在我们添加元素时进行一些代价高昂的增长操作。
同样,如果列表非常大,自动增长操作可能会分配比实际最大大小所需更多的内存。这是因为每次增长的数量是按到目前为止大小的比例计算的。因此,对于大型列表,这可能会导致内存浪费。
4.2 构建小型多ArrayList
如果我们有很多小集合,那么ArrayList的自动容量可能会造成很大比例的内存浪费。假设ArrayList使用10的大小和较少的元素,但我们只存储2或3。这意味着70%的内存浪费,如果我们有大量这样的列表,这可能很重要。
预先设置容量可以避免这种情况。
5. 避免浪费
我们应该注意,对于支持随机访问的灵活大小的对象容器,ArrayList是一个很好的解决方案。它消耗的内存比数组略多,但提供了一组更丰富的操作。
在某些用例中,尤其是在大量原始类型值集合周围,标准数组可能更快并且使用更少的内存。
同样,对于存储不需要由索引访问的可变数量的元素,LinkedList可以提高性能。它没有任何内存管理开销。
6. 总结
在这篇简短的文章中,我们了解了ArrayList的容量与数组大小之间的差异。我们还研究了何时应该使用容量初始化ArrayList,以及它在内存使用和性能方面的优势。
与往常一样,本教程的完整源代码可在GitHub上获得。