【问题标题】:Java CharAt() and deleteCharAt() performanceJava CharAt() 和 deleteCharAt() 性能
【发布时间】:2011-09-21 15:05:58
【问题描述】:

我一直想知道java中String/StringBuilder/StringBuffer的charAt函数的实现 那有什么复杂性? StringBuffer/StringBuilder 中的 deleteCharAt() 呢?

【问题讨论】:

  • 您有机会查看这些方法的源代码吗?
  • 是的,既然你已经有了一个 linux 系统供你使用,你应该抓住 open-JDK 并自己探索它。
  • @HoverCraft 我刚刚检查了来源 .. 感谢您将我指向那个 @Mazyod .. 好的
  • 如果你认为你需要一个线程安全的 StringBuffer,那么你的设计可能很糟糕(或者你可以使用另一个类,比如 StringWriter)

标签: java performance string complexity-theory stringbuilder


【解决方案1】:

对于StringStringBufferStringBuildercharAt() 是一个常数时间运算。

对于StringBufferStringBuilderdeleteCharAt() 是线性时间运算。

StringBufferStringBuilder 具有非常相似的性能特征。主要区别在于前者是synchronized(因此是线程安全的),而后者不是。

【讨论】:

  • 对于String,deleteCharAt()是一个O(n)操作,其中n是字符串的大小 String是不可变的,不能删除任何东西(没有这个函数),对于 StringBuffer/Builder,deleteCharAt 是一个数组代码 System.arraycopy(value, index+1, value, index, count-index-1);,而 memmove 可以是 impl。在硬件的帮助下,技术上仍然是 O(n)。
  • 我是个白痴,没有意识到String#deleteCharAt() 的不存在。谢谢。
  • @sleeparrow Java 不是 JS; Java 在内部使用 UTF-16,而不是 UTF-8。如果您阅读了各种方法的源代码,就会很清楚,例如,charAt() 是一个常量时间操作,因为它只是索引到一个char 数组。 String#charAt(int)StringBuilder#charAt(int)
  • 哈哈,我没有意识到这是一个 Java 问题。对不起。
  • @MattBall 但是 UTF-16 也不是固定大小的,所以虽然 charAt 仍然可以在固定时间内工作,但是对于需要 32 位的字符,操作只会返回真实字符的一半
【解决方案2】:

让我们依次看一下每个方法对应的实际java实现(仅相关代码)。这本身就会回答他们的效率问题。

String.charAt:

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

正如我们所见,这只是一个恒定时间操作的单个数组访问。

StringBuffer.charAt:

public synchronized char charAt(int index) {
  if ((index < 0) || (index >= count))
    throw new StringIndexOutOfBoundsException(index);
  return value[index];
}

同样,单数组访问,所以是恒定时间操作。

StringBuilder.charAt:

public char charAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    return value[index];
}

同样,单数组访问,所以是恒定时间操作。尽管这三种方法看起来都一样,但还是有一些细微的差别。例如,只有 StringBuffer.charAt 方法是同步的,其他方法是不同步的。类似地,if check 对于 String.charAt 略有不同(猜猜为什么?)。仔细观察这些方法实现本身,我们会发现它们之间的其他细微差别。

现在,让我们看看 deleteCharAt 的实现。

String 没有 deleteCharAt 方法。原因可能是它是一个不可变的对象。因此,公开一个明确表明此方法修改对象的 API 可能不是一个好主意。

StringBuffer 和 StringBuilder 都是 AbstractStringBuilder 的子类。这两个类的 deleteCharAt 方法将实现委托给其父类本身。

StringBuffer.deleteCharAt :

  public synchronized StringBuffer deleteCharAt(int index) {
        super.deleteCharAt(index);
        return this;
    }

StringBuilder.deleteCharAt :

 public StringBuilder deleteCharAt(int index) {
        super.deleteCharAt(index);
        return this;
    }

AbstractStringBuilder.deleteCharAt:

  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)。

【讨论】:

    【解决方案3】:

    charAt 方法是O(1)

    假设您从N 字符StringBuffer / StringBuilder 中删除随机字符,StringBuilderStringBuffer 上的deleteCharAt 方法平均为O(N)。 (平均而言,它必须移动剩余字符的一半以填补被删除字符留下的“洞”。多次操作没有摊销;见下文。)但是,如果你删除最后一个字符,代价为O(1)

    String 没有 deleteCharAt 方法。


    理论上,StringBuilderStringBuffer 可以针对在“通过”缓冲区中插入或删除多个字符的情况进行优化。他们可以通过在缓冲区中维护一个可选的“间隙”并在其中移动字符来做到这一点。 (IIRC,emacs 以这种方式实现其文本缓冲区。)这种方法的问题是:

    • 它需要更多空间,用于说明间隙位置的属性以及间隙本身。
    • 它使代码更加复杂,并减慢了其他操作。例如,charAt 必须将offset 与间隙的起点和终点进行比较,并在获取字符数组元素之前对实际的索引值进行相应的调整。
    • 只有在应用程序对同一个缓冲区执行多次插入/删除操作时,它才会有所帮助。

    毫不奇怪,标准的StringBuilder / StringBuffer 类中没有实现这种“优化”。但是,自定义 CharSequence 类可以使用这种方法。

    【讨论】:

      【解决方案4】:

      charAt 非常快(并且可以使用 String 的内在函数),它是数组的简单索引。 deleteCharAt 需要一个数组复制,因此删除一个字符不会很快。

      【讨论】:

        【解决方案5】:

        既然我们都知道字符串在JDK中是作为字符数组实现的,它实现了randomAccess接口。因此charAt 的时间复杂度应该是 int O(1)。与其他数组一样,删除操作的时间复杂度为O(n)

        【讨论】:

          【解决方案6】:

          以上所有回复的摘要: charAt 是 O(1) 因为它只是访问数组的索引 deleteCharAt 在最坏的情况下可能是 O(N),因为它会为它复制整个数组。

          【讨论】:

            猜你喜欢
            • 2018-04-12
            • 2021-09-03
            • 1970-01-01
            • 1970-01-01
            • 2014-10-24
            • 2018-06-17
            • 2015-04-11
            • 2017-02-26
            • 2013-09-30
            相关资源
            最近更新 更多