ArrayList 底层数组成本
ArrayList Underlying Array Cost
我最近在阅读 Java 的 ArrayList。根据我的理解,当 ArrayList 达到其容量时,它会调用其调整大小方法并创建一个新的基础数组,该数组的大小是原始数组的 double。由于这种情况,这种方式的插入可以被视为 O(n),但平均而言它仍然是 O(1)。但是,我对它具体增加到两倍大小的原因感到困惑。
是不是增加3倍于原来的容量会更好?
重要的是容量增加了一个常数。这个因素是什么并不重要:1.5 和 3 一样好。摊销成本将为 O(1) - 常数时间。但是,不同的因素会导致不同的常数。
如果您将因素考虑在内进行数学计算,则摊销时间成本为 O(f/(f-1)),其中 f 是因素。如果加倍的时间成本为 2,则三倍的时间成本为 1.5。
大幅增加容量会减少调整大小操作,但会造成更多浪费 space。您必须找到一个平衡点:减少 25% 的执行时间值得增加 50% 的存储开销吗?
有关调整因子对摊销成本和渐近复杂性的影响的答案,请参阅 。
让我从一个不同的、更实用的角度来解决这个问题。
标准库的设计者必须找到适用于范围广泛的一般工作负载的良好默认行为。有时,经过大量考虑和基准测试后,他们会更改这些内容(请参阅 HashMap 或 String 的实现细节演变)。总有一个权衡,你不可能对每个用例都绝对正确。
如果您发现默认行为不适合您,作为应用程序开发人员,您有能力也有义务调整默认设置。
在手头的例子中,如果您已经知道您的 ArrayList 将需要增长到超过默认的初始容量,您可以告诉构造函数预先分配一个更大的容量。稍后您还可以调用 ensureCapacity
和 trimToSize
来调整它。
请记住,O
表示法只考虑到一个常数的缩放行为。虽然增加 3 倍会减少运行时间,但缩放行为仍然是线性的 O(1)。如果插入的成本是 1,它不会变得更好。所以摊销成本永远不会比插入的 O(1) 更好。
如果你看一下证明,你会发现用 2x 缩放时每个元素的成本是 3,用 3x 缩放时每个元素的成本是 2.5,两者都是常数,所以 O(1)。
缩放因子 s 的平均成本是:(2s - 1) / (s - 1)
,对于任何缩放因子 s > 1,其成本都是 O(1)。即使是非常小的缩放因子,如 1.1,同时具有每个元素的成本为 12,仍会有 O(1) 的摊销插入成本。
我最近在阅读 Java 的 ArrayList。根据我的理解,当 ArrayList 达到其容量时,它会调用其调整大小方法并创建一个新的基础数组,该数组的大小是原始数组的 double。由于这种情况,这种方式的插入可以被视为 O(n),但平均而言它仍然是 O(1)。但是,我对它具体增加到两倍大小的原因感到困惑。 是不是增加3倍于原来的容量会更好?
重要的是容量增加了一个常数。这个因素是什么并不重要:1.5 和 3 一样好。摊销成本将为 O(1) - 常数时间。但是,不同的因素会导致不同的常数。
如果您将因素考虑在内进行数学计算,则摊销时间成本为 O(f/(f-1)),其中 f 是因素。如果加倍的时间成本为 2,则三倍的时间成本为 1.5。
大幅增加容量会减少调整大小操作,但会造成更多浪费 space。您必须找到一个平衡点:减少 25% 的执行时间值得增加 50% 的存储开销吗?
有关调整因子对摊销成本和渐近复杂性的影响的答案,请参阅
让我从一个不同的、更实用的角度来解决这个问题。
标准库的设计者必须找到适用于范围广泛的一般工作负载的良好默认行为。有时,经过大量考虑和基准测试后,他们会更改这些内容(请参阅 HashMap 或 String 的实现细节演变)。总有一个权衡,你不可能对每个用例都绝对正确。
如果您发现默认行为不适合您,作为应用程序开发人员,您有能力也有义务调整默认设置。
在手头的例子中,如果您已经知道您的 ArrayList 将需要增长到超过默认的初始容量,您可以告诉构造函数预先分配一个更大的容量。稍后您还可以调用 ensureCapacity
和 trimToSize
来调整它。
请记住,O
表示法只考虑到一个常数的缩放行为。虽然增加 3 倍会减少运行时间,但缩放行为仍然是线性的 O(1)。如果插入的成本是 1,它不会变得更好。所以摊销成本永远不会比插入的 O(1) 更好。
如果你看一下证明,你会发现用 2x 缩放时每个元素的成本是 3,用 3x 缩放时每个元素的成本是 2.5,两者都是常数,所以 O(1)。
缩放因子 s 的平均成本是:(2s - 1) / (s - 1)
,对于任何缩放因子 s > 1,其成本都是 O(1)。即使是非常小的缩放因子,如 1.1,同时具有每个元素的成本为 12,仍会有 O(1) 的摊销插入成本。