【问题标题】:Space complexity of adding Java Strings char by char逐字符添加Java字符串的空间复杂度
【发布时间】:2018-12-10 03:11:31
【问题描述】:

当通过“加法”循环通过一个字符一个字符地构建一个Java字符串时,可以观察到执行这个操作的时间复杂度很差:它是二次的 em> O(n^2) 时间。但是,我想知道通过循环“逐个添加”字符串的空间复杂度是否也很差。

这是一个程序的最小示例,该程序通过逐字符添加和秒表计时来执行构建字符串。下面显示的结果清楚地显示了二次曲线:

/**Create a String of n 'A's char-by-char.
 * @param n Number of 'A's to be appended
 * @return The finished string
 */
public static String appendString(int n) {
    String str = "";
    // Timed run starts here
    long t = System.currentTimeMillis();
    // String concatenation occurs here
    for (int k = 0; k < n; k++)
        str += "A";
    t = System.currentTimeMillis() - t;
    // Timed run ends
    System.out.printf("%d\t%d\n", n, t);
    return str;
}

虽然我可以得到程序的时间复杂度,但我想知道逐个字符构建字符串的空间复杂度是多少?

首选解决内存管理的详细答案。 (我怀疑它也是二次的,因为字符串是不可变的,每次你必须为不断增加的字符串分配新的内存)

注意:这不是String concatenation complexity in C++ and Java 的重复,因为它没有解决空间复杂度。我特别要求进行详细的空间复杂度分析。

【问题讨论】:

  • 嗯,短期内是二次的。但是旧的未使用的字符串应该被垃圾回收,因此所需的内存空间应该只与在任何给定时间需要的字符串的大小(长度)成正比。
  • 非线性时间,线性空间(内存占用)。我也不确定“平均”,但从长远来看,是的,我会这么认为。
  • 一般情况下很难说。当需要优化时,我考虑过编写自己的字符串类,但到目前为止我已经能够避免它。我认为“更好的方法”取决于应用程序和使用模型。作为通用实现,String 和 StringBuilder 是很好实现的类。
  • 我认为这种说法可能过于宽泛。它使我的“过早优化”传感器跳闸。以简单的方法实现代码,只应用明显的优化。然后性能测试真实运行的代码,只在需要的地方进行更多优化。如果按字符构建字符串似乎是正确的,那么使用该方法就可以了。

标签: java string complexity-theory string-concatenation space-complexity


【解决方案1】:

它使用二次空间。或者更确切地说,分配了一个二次方的空间,因为循环的每次迭代都会(至少在 JIT 没有做聪明的代码中)分配一个新的 char 数组:

new char[1]
new char[2]
new char[3]
// Etc.

二次时间性能的原因是将字符串复制到这些越来越大的数组中。

【讨论】:

  • 请解释二次空间。除了2n+1 的空间,还需要多少空间?
  • 我不会说它会被推荐。实施某些东西可能只是一种权宜之计。
  • @lexicore 它将运行new char[1]new char[2]new char[3] 等,以创建新的字符串实例。也许 JIT 可以对其进行优化以更有效地做事,但这是幼稚的方法。
  • 这肯定不对。一旦你进入非常大的字符串,垃圾收集器将收集除最大的两个之外的任何数组。所以空间要求必须是线性的,而不是二次的。当然,当程序启动时,它一开始看起来是二次的。
  • @DawoodibnKareem 我认为这取决于您是否将 GC 的操作视为此代码语义的一部分。这就是我添加“或者,更确切地说...”的原因:二次空间是分配的;何时(或是否)清理超出程序员的控制范围。
【解决方案2】:

您的代码效率很低,因为创建了一个隐式 StringBuilder 以在循环中添加单个字符。这个,

for (int k = 0; k < n; k++)
    str += "A";

相当于

for (int k = 0; k < n; k++)
    str = new StringBuilder(str).append("A").toString();

您可以改用单个StringBuilder 来大大提高空间(和时间)性能(并且您可以显式调整它的大小)。喜欢,

StringBuilder sb = new StringBuilder(n);
for (int k = 0; k < n; k++)
    sb.append("A");
String str = sb.toString();

或者,在 Java 8+ 中,您可以使用生成器并将其限制为 n 次并收集它。喜欢,

return Stream.generate(() -> "A").limit(n).collect(Collectors.joining());

【讨论】:

  • 使用char[] cs = new char[n]; Arrays.fill(cs, 'A'); String str = new String(cs); 的方法可能更有效。
  • 为了让您的问题成为主题,您能否也说明时间复杂度?我要求对 Java 在执行低效代码时如何管理内存进行详细的空间复杂性分析。
  • 时间复杂度是O(n)(就像你的解决方案一样)。在讨论时间复杂度时,常数因子被丢弃。我们可以通过使用更长的A序列将其减少到O(log n)
猜你喜欢
  • 2013-04-13
  • 2018-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多