我知道容量是 ArrayList 中可能包含也可能不包含引用对象的值的元素或可用空间的数量。我试图更多地了解容量的概念。
所以我有三个问题:
1)从内存的角度定义容量代表什么的好方法是什么?
...分配给 ArrayList 的(连续?)内存?
... ArrayLists 在(堆?)上的内存占用?
2)如果上述情况属实,那么改变容量需要某种方式的内存管理开销?
3)任何人都有一个例子,其中#2是或可能是性能问题?除了可能不断调整容量的大量大型 ArrayList 之外?
ArrayList 是这样实现的:
class ArrayList {
private Object[] elements;
}
容量是该数组的大小。
现在,如果您的容量为 10,并且您要添加第 11 个元素,则 ArrayList 将执行以下操作:
Object[] newElements = new Object[capacity * 1.5];
System.arraycopy(this.elements, newElements);
this.elements = newElements;
因此,如果您从小容量开始,ArrayList 最终会在您不断添加元素时创建一堆数组并为您复制东西,这并不好。
另一方面,如果您指定容量为 1,000,000 并且仅向 ArrayList 添加 3 个元素,那也有点糟糕。
经验法则:如果您知道容量,请指定它。如果您不确定但知道上限,请指定。如果您不确定,请使用默认值。
容量正如您所描述的那样 - 分配给 ArrayList 用于存储值的连续内存。ArrayList 将所有值存储在一个数组中,并自动为您调整数组的大小。这会在调整大小时产生内存管理开销。
如果我没记错的话,当您尝试添加一个超出容量的元素时,Java 会将 ArrayList 的后备数组的大小从 N 增加到 2N + 2。我不知道当您使用该insert方法(或类似方法)在超出容量末端的特定位置插入时它会增加多少大小,甚至不知道它是否允许这样做。
这里有一个例子可以帮助你思考它是如何工作的。将 s之间的每个空间想象|成后备数组中的一个单元:
| | |
size = 0(不包含元素),容量 = 2(可以包含 2 个元素)。
|1| |
size = 1(包含 1 个元素),容量 = 2(可以包含 2 个元素)。
|1|2|
大小 = 2,容量 = 2。添加另一个元素:
|1|2|3| | | |
大小增加 1,容量增加至 6 (2 * 2 + 2)。对于大型数组,这可能会很昂贵,因为分配一个大的连续内存区域可能需要一些工作(与分配许多小块内存的 LinkedList 不同),因为 JVM 需要搜索适当的位置,并且可能需要要求操作系统提供更多内存。将大量值从一个地方复制到另一个地方也很昂贵,一旦找到这样的区域就会这样做。
我的经验法则是:如果您知道需要的容量,请使用 ArrayList,因为只有一次分配并且访问速度非常快。如果您不知道所需的容量,请使用 LinkedList,因为添加新值始终需要相同的工作量,并且不涉及复制。
1)从内存的角度定义容量代表什么的好方法是什么?
...分配给 ArrayList 的(连续?)内存?
是的,一个 ArrayList 由一个数组备份,它代表内部数组的大小。
... ArrayLists 在(堆?)上的内存占用?
是的,阵列容量越大,arraylist 使用的内存就越多。
2)如果上述情况属实,那么改变容量需要某种方式的内存管理开销?
这是。当列表变得足够大时,分配一个更大的数组并复制内容。先前的数组可能会被丢弃并标记为垃圾收集。
3)任何人都有一个例子,其中#2是或可能是性能问题?除了可能不断调整容量的大量大型 ArrayList 之外?
是的,如果您创建初始容量为 1 的 ArrayList (例如)并且您的列表远远超出此范围。如果您预先知道要存储的元素数量,则最好请求该大小的初始容量。
但是我认为这在您的优先级列表中应该很低,虽然数组复制可能经常发生,但它在 Java 的早期阶段就已经优化,不应该成为问题。我认为最好选择一个正确的算法。记住:过早的优化是万恶之源