【问题标题】:StringBuffer and amortizationStringBuffer 和摊销
【发布时间】:2013-07-02 02:54:29
【问题描述】:

ArrayList 中,添加操作是摊销操作。因此,在阅读StringBuffer 时,我想到了为什么StringBuffer 没有摊销。假设我在一个字符串缓冲区对象上使用附加操作,那么它应该能够在它的底层数组实现中添加那么多没有字符。但相反,我在源代码中发现我们有

System.arraycopy(chars, 0, value, count, chars.length);

在字符串缓冲区的追加操作中。所以我的问题是 StringBuffer 不能摊销,这样它就可以减少 O(N) 的复杂性吗?

【问题讨论】:

标签: java arrays arraylist stringbuffer amortized-analysis


【解决方案1】:

归根结底,您仍在将 N 个引用从某个内存位置 A 移动到某个其他内存位置 B。但是,我相信 System.arraycopy 的速度足够快,以至于来自StringBuffer。但是,这取决于你是使用append 还是insert

回想一下,ArrayList 可以通过两种方式执行add:使用单个对象,或者通过从特定索引点向下移动元素。两者都有不同的表现。

为了解决这个问题,(最终)StringBuffer 将致电System.arraycopy()。这个实现实际上是依赖于 JVM 的(它是一个本地调用),但可能是它非常快。除此之外,没有什么可以真正减慢StringBuffer.append() 的性能,除了要复制非常大的非连续内存区域。

ArrayList.add(E element) 将花费 O(1) 时间,但可能会更改为 O(N),因为它必须增​​加后备数组以容纳更多元素;但是,如果它不必这样做,那么插入时间大约为 O(1)(它将元素添加到数组的末尾)。

ArrayList.add(int index, E element) 在最佳情况下可能是 O(1),但在平均和最坏情况下可能是 O(N - index),因为它必须向下移动以放置 E 的元素数量。

总结一下:

  • StringBuffer.append() 的代码可以看作是平均 O(N),因为它确实复制了数组。但是,此操作仍然很快,并且仅取决于您要移动的数据有多大。

  • StringBuffer.insert() 的代码不同,在最好的情况下很容易成为分期 O(1),在最坏的情况下是 O(N)(因为它使得 两次调用System.arraycopy())。在给定点插入元素意味着您必须将其他所有内容向下移动,这不是一个便宜的操作,无论您的代码有多快。

我相信,根据您使用的方法,您确实具有摊销性能。您只需要确定您正在执行的操作即可知道性能将如何。

【讨论】:

    【解决方案2】:

    实际上StringBufferArrayList 的工作方式相同,正如您所指出的,ArrayList 中的add 操作是O(1) 摊销

    ArrayList 中,当您添加一个元素时,您还有一个ensureCapacity 方法,如果容量不足,则会分配一个新数组并将数据复制到其中。然而,这种重新分配操作很少发生,所以你可以考虑加法需要O(1),即使是 K 的 1 倍,它也需要O(n)

    【讨论】:

    • 我检查了ensureCapacity的代码,发现这也使用了System.arraycopy(value, 0, newData, 0, count);我认为这也会将整个字符数组复制到新位置。所以我的疑问仍然存在,如果我的理解有误,你可以详细说明一下。
    • 你是对的。那就是当底层数组需要增长时,O(n) 操作在 K 上发生 1 次。
    【解决方案3】:

    如果您进一步查看源代码:

    public AbstractStringBuilder append(char[] str) {
        int len = str.length;
        ensureCapacityInternal(count + len);  // expand array if needed
        System.arraycopy(str, 0, value, count, len); 
        count += len;
        return this;
    }
    

    这正是 arraylist 的工作原理。

        System.arraycopy(str, 0, value, count, len);
    

    告诉我们从 str 复制到从 count 开始的值(stringBuffer 中的当前结束位置)。只有 len 是附加 str 的长度被复制。

    【讨论】:

    • 这不能回答我的问题。我担心将字符串从一个地方复制到另一个地方会占用更多内存(在执行操作时),这意味着读取字符串的所有元素。这意味着复杂度将是 o(n)。
    【解决方案4】:

    您混合了取决于数据结构大小(ArrayList / StringBuilder 长度)的复杂性和取决于输入大小的复杂性。

    ArrayList 的O(1) 是指将单个元素添加到列表的内容并摊销O(1)。显然,将 n 个元素添加到列表中将是 n * O(1)。 StringBuilder 也是如此。在大多数情况下,添加单个字符是O(1)(除非必须扩展内部数组。但是添加一个字符数组需要复制数组的值(实际上数组的批量复制应该非常快,肯定比附加单个字符要快) .

    O(1) 可以实现列表连接 - 附加时 - 使用链表。但是结果很容易受到每个连接子列表的变化的影响,……。 StringBuilder 总是想避免。此外,作为 String 实现的字符链接列表将非常低效。

    【讨论】:

      猜你喜欢
      • 2013-11-08
      • 1970-01-01
      • 2016-03-16
      • 2015-05-04
      • 2018-02-13
      • 2010-09-17
      • 2022-01-15
      相关资源
      最近更新 更多