在ArrayList
中,添加操作是摊销操作。因此,在阅读StringBuffer
时,我想到了为什么StringBuffer
没有摊销。假设我对字符串缓冲区对象使用追加操作,那么它应该能够在其底层数组实现中添加许多字符。但相反,我在源代码中找到了
System.arraycopy(chars, 0, value, count, chars.length);
追加字符串缓冲区的操作。所以我的问题是StringBuffer
不能摊销,这样可以减少O(N)的复杂性吗?
答案 0 :(得分:1)
实际上,StringBuffer
和ArrayList
的工作方式与您指出的相同,add
ArrayList
O(1)
的操作是ArrayList
摊销
在ensureCapacity
中,当您添加元素时,您还有一个O(1)
方法,如果容量不足,则会分配一个新数组并将数据复制到其中。然而,这种重新分配的操作很少发生,所以你可以认为即使比K超过1次,添加也需要O(n)
,需要{{1}}。
答案 1 :(得分: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 - 指数),因为它必须向下移动到{{1}的元素数量in。
总结一下:
E
的代码可以看作是amoritized O(N),因为它会复制数组。但是,此操作仍然很快,并且实际上只取决于您移动的数据的大小。
StringBuffer.append()
的代码不同,在最好的情况下很容易成为一个非定义的O(1),而在最坏的情况下很容易成为O(N)(因为它)对StringBuffer.insert()
进行两次调用。在给定点插入元素意味着你必须将其他所有内容都移动,这不是一个便宜的操作,无论你的代码有多快。
我相信,根据您使用的方法,您做具有非正式性能。你只需要确定你正在做的操作来判断性能是什么。
答案 2 :(得分:0)
如果您进一步了解源代码:
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);
告诉我们从count开始复制到值(从stringBuffer中的当前结束位置开始)。仅复制附加str的长度len
。
答案 3 :(得分:0)
根据数据结构的大小(ArrayList / StringBuilder长度)和复杂程度混合复杂性,具体取决于输入的大小。
ArrayList的O(1)
是指向列表的内容添加单个元素,并按摊销O(1)
。显然,在列表中添加n个元素将是n * O(1)
。 StringBuilder也是如此。在大多数情况下,添加单个字符是O(1)
(除非必须扩展内部数组。但是添加一个char数组需要复制数组的值(实际上,数组的批量复制应该非常快,当然比单个附加更快)字符)。
O(1)
可以在使用链表时附加时实现。但结果很容易受到每个连接子列表中的变化的影响,某事。 StringBuilder总是希望避免。此外,链接的字符串列表作为字符串实现将是非常低效的。