为什么Vector的经典实现(适用于Java人员的ArrayList)在每次扩展时将其内部数组大小加倍,而不是将其增加三倍或四倍?
答案 0 :(得分:21)
计算插入矢量的平均时间时,需要允许不增长的插入和不断增长的插入。
调用要插入 n 项 o total 的操作总数,以及平均 o 平均值子> 的
如果您插入 n 项目,并且根据需要增长了 A ,那么 o total = n +ΣA i [0<我< 1 + ln A n] 操作。在最坏的情况下,您使用 1 / A 分配的存储空间。
直观地, A = 2 意味着在最坏的情况下你有 o total = 2n ,所以 o average 是O(1),最坏的情况是你使用50%的已分配存储空间。
对于较大的 A ,您的 o 总计 较低,但存储空间较多。
对于较小的 A , o 总计 较大,但您不会浪费太多存储空间。只要它几何增长,它仍然是O(1)摊销的插入时间,但常数会变高。
对于生长因子1.25(红色),1.5(青色),2(黑色),3(蓝色)和4(绿色),这些图表显示了点和平均尺寸效率(尺寸/分配空间的比率;越多越好) )在左侧和时间效率(插入/操作的比率;越多越好)插入400,000项的权利。在调整大小之前,所有生长因子都达到了100%的空间效率; A = 2 的情况表明时间效率在25%到50%之间,空间效率约为50%,这对大多数情况都有好处:
对于Java等运行时,数组是零填充的,因此要分配的操作数与数组的大小成正比。考虑到这一点,可以减少时间效率估算之间的差异:
答案 1 :(得分:4)
数组(或字符串)大小的指数倍增是在阵列中有足够的单元格和浪费太多内存之间的良好折衷。
假设我们从10个元素开始:
1 - 10
2 - 20
3 - 40
4 - 80
5 - 160
当我们将尺寸增加三倍时,我们增长得太快
1 - 10
2 - 30
3 - 90
4 - 270
5 - 810
在实践中,你可能会成长10或12倍。如果你三倍,你可能会做7或8次 - 重新分配的运行时命中,这几次是足够小的担心,但你更有可能完全超过所需的大小。
答案 2 :(得分:3)
如果你要分配一个不寻常大小的内存块,那么当该块被解除分配时(或者因为你正在调整它或者它得到了GC),内存中会出现一个异常大小的漏洞,这可能会导致内存管理员的头痛。因此,通常首选以2的幂分配内存。在某些情况下,底层内存管理器只会为您提供一定大小的块,如果您请求一个奇怪的大小,它将向上舍入到下一个更大的大小。因此,不是要求470个单位,无论如何都要回到512,然后一旦你使用了所要求的全部470个,再次调整大小,不妨只要求512开始。
答案 3 :(得分:3)
任何倍数都是妥协。让它太大,你浪费太多的记忆。使它太小,你浪费了很多时间进行重新分配和复制。我想那里加倍是因为它有效并且很容易实现。我还看到了一个类似STL的专有库,它使用1.5作为乘法器 - 我猜它的开发人员认为会浪费太多内存。
答案 4 :(得分:2)
如果您询问Vector和ArrayList的特定于Java的实现,那么每次扩展时不一定要加倍。
来自Javadoc for Vector:
每个向量都会尝试通过维护
capacity
和capacityIncrement
来优化存储管理。容量始终至少与矢量大小一样大;它通常更大,因为随着组件被添加到向量中,向量的存储以capacityIncrement
的大小增加。应用程序可以在插入大量组件之前增加向量的容量;这减少了增量重新分配的数量。
Vector的一个构造函数允许您指定Vector的初始大小和容量增量。 Vector类还提供ensureCapacity(int minCapacity)
和setSize(int newSize)
,用于手动调整Vector的最小大小,并自行调整Vector的大小。
ArrayList类非常相似:
每个
ArrayList
实例都有容量。容量是用于存储列表中元素的数组的大小。它始终至少与列表大小一样大。当元素添加到ArrayList时,其容量会自动增加。除了添加元素具有恒定的摊销时间成本这一事实之外,未指定增长政策的细节。在使用ensureCapacity操作添加大量元素之前,应用程序可以增加
ArrayList
实例的容量。这可能会减少增量重新分配的数量。
如果您询问向量的一般实现,那么选择增加大小和权衡是多少。通常,向量由数组支持。数组具有固定的大小。要调整向量的大小,因为它已满意味着您必须将数组的所有元素复制到一个新的更大的数组中。如果你使新阵列太大,那么你已经分配了永远不会使用的内存。如果它太小,可能需要很长时间才能将旧数组中的元素复制到新的更大的数组中 - 这是您不希望经常执行的操作。
答案 5 :(得分:2)
就个人而言,我认为这是一种仲裁选择。我们可以使用base e而不是base 2(而不是通过(1 + e)将多个大小加倍。)
如果你要向向量添加大量变量,那么拥有一个高基数(减少你将要做的复制的数量)将是有利的。在另一方面,如果你需要存储平均只有少数成员,那么低基数就可以了,减少了开销,从而加快了速度。
Base 2是妥协。
答案 6 :(得分:-1)
由于所有具有相同的大O性能配置文件,因此没有性能原因可以使倍增倍数增加三倍或四倍。然而,从绝对意义上说,在正常情况下,翻倍会更有效率。