我一直想知道java中String / StringBuilder / StringBuffer的charAt函数的实现 那是什么样的复杂性? 还有什么关于StringBuffer / StringBuilder中的deleteCharAt()?
答案 0 :(得分:26)
对于String
,StringBuffer
和StringBuilder
,charAt()
是一项固定时间操作。
对于StringBuffer
和StringBuilder
,deleteCharAt()
是线性时间操作。
StringBuffer
和StringBuilder
具有非常相似的性能特征。主要区别在于前者是synchronized
(因此是线程安全的),而后者则不是。
答案 1 :(得分:16)
让我们依次查看每个方法的相应实际java实现(仅相关代码)。这本身就会回答他们的效率。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
正如我们所看到的,它只是一个单一的数组访问,这是一个恒定时间操作。
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
再次,单阵列访问,所以常量时间操作。
public char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
再次,单阵列访问,所以常量时间操作。即使所有这三种方法看起来都相同,但也存在一些细微差别。例如,只有StringBuffer.charAt方法是同步的,而不是其他方法。类似地如果检查对于String.charAt略有不同(猜猜为什么?)。仔细看看这些方法实现本身就会给我们带来其他的细微差别。
现在,让我们看一下deleteCharAt实现。
String没有deleteCharAt方法。原因可能是它是一个不可变的对象。因此,暴露一个明确指示此方法修改对象的API可能不是一个好主意。
StringBuffer和StringBuilder都是AbstractStringBuilder的子类。这两个类的deleteCharAt方法将实现委托给它的父类本身。
public synchronized StringBuffer deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}
public StringBuilder deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}
public AbstractStringBuilder deleteCharAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
System.arraycopy(value, index+1, value, index, count-index-1);
count--;
return this;
}
仔细研究AbstractStringBuilder.deleteCharAt方法可以发现它实际上正在调用System.arraycopy。在最坏的情况下,这可以是O(N)。所以deleteChatAt方法是O(N)时间复杂度。
答案 2 :(得分:7)
charAt
方法为O(1)
。
假设您要删除deleteCharAt
字符StringBuilder
中的随机字符,StringBuffer
和O(N)
上的N
方法平均为StringBuffer
} / StringBuilder
。 (平均而言,它必须移动剩余字符的一半以填充被删除字符留下的“洞”。没有多次操作的摊销;见下文。)但是,如果你删除最后一个字符,费用为O(1)
。
deleteCharAt
没有String
方法。
理论上,StringBuilder
和StringBuffer
可以针对通过缓冲区“通过”插入或删除多个字符的情况进行优化。他们可以通过在缓冲区中保持可选的“间隙”并在其上移动字符来实现此目的。 (IIRC,emacs以这种方式实现其文本缓冲。)这种方法的问题是:
charAt
必须将offset
与间隙的起点和终点进行比较,并在获取字符数组元素之前对实际索引值进行相应的调整。毫不奇怪,此“优化”尚未在标准StringBuilder
/ StringBuffer
类中实施。但是,自定义CharSequence
类可以使用此方法。
答案 3 :(得分:5)
charAt
超快(并且可以使用String的内在函数),它是一个简单的数组索引。 <{1}}需要一个arraycopy,因此删除一个char将不会很快。
答案 4 :(得分:0)
由于我们都知道该字符串在JDK中作为字符数组实现,因此实现了randomAccess
接口。因此charAt
的时间复杂度应为int O(1)。与其他阵列一样,删除操作的时间复杂度为O(n)
。