为什么第二个代码比第一个代码更有效?

时间:2018-12-27 06:04:14

标签: java algorithm time-complexity

我对两个代码感到困惑,为什么我要在这里给出的第二个代码比第一个更有效。

这两个代码都只是反转一个字符串,但是第一个代码比另一个代码慢,而且我不明白为什么。

第一个代码是:

String reverse1(String s) {
    String answer = "";
    for(int j = s.length() - 1; j >= 0; j--) {
        answer += s.charAt(j); 
    }
    return answer;
}

第二个代码是:

String reverse2(String s) {
    char answer[] = new char[s.length()]; 
    for(int j = s.length() - 1; j >= 0; j--) {
        answer[s.length() - j - 1] = s.charAt(j);
    }
    return new String(answer);
}

我无法理解第二个代码比第一个代码更有效率,对此我将不胜感激。

7 个答案:

答案 0 :(得分:3)

第一个代码声明

String answer;

字符串是不可变的。因此,每个追加操作都会重新分配整个字符串,将其复制,然后复制为新字符。

第二个代码声明

char answer[];

数组是可变的,因此每次迭代仅复制一个字符。最终的字符串仅创建一次,而不是在循环的每次迭代中创建一次。

答案 1 :(得分:1)

您的问题也许很难准确回答,部分原因是答案将取决于第一个版本的实际实现。反过来,这取决于您使用的Java版本以及编译器决定做什么。

假设编译器在编写时保持逐字记录第一个版本,那么是的,第一个版本可能效率更低,因为它将需要为反转过程中的每个步骤分配一个新的字符串。相反,第二个版本仅维护单个字符数组。

但是,如果编译器足够聪明,可以使用StringBuilder,那么答案就会改变。考虑以下第一个版本:

String reverse1(String s) {
    StringBuilder answer = new StringBuilder();
    for (int j = s.length() - 1; j >= 0; j--) 
        answer.append(s.charAt(j));

    return answer;
}

在幕后,StringBuilder是使用字符数组实现的。因此,调用StringBuilder#append有点类似于第二个版本,即它只是在缓冲区的末尾添加了新字符。

因此,如果您的第一个版本使用文字String执行,那么它的效率要比第二个版本低,但是使用StringBuilder可能与第二个版本相提并论。

答案 2 :(得分:0)

字符串是不可变的。每当您执行answer += s.charAt(j);时,它都会创建一个新对象。尝试使用-XX:+PrintGCDetails打印GC日志,看看延迟是否是由次要GC引起的。

答案 3 :(得分:0)

String对象是不可变的,每次执行添加操作时,您都会创建另一个对象,分配空间等,因此当您需要连接多个字符串时,效率很低。

您的char数组方法非常适合您的特定需求,但是如果您需要更多通用字符串连接支持,则可以考虑使用StringBuilder

答案 4 :(得分:0)

在此代码中,您将在每次循环迭代中创建一个新的String对象,因为String是不可变的类

String reverse1(String s) {
    String answer = "";
    for (int j = s.length() - 1; j >= 0; j--)
        answer += s.charAt(j);
    return answer;
}

在此代码中,您已经为char数组分配了内存,您的代码将在最后一行仅创建单个String,因此效率更高

String reverse2(String s) {
    char answer[] = new char[s.length()];
    for (int j = s.length() - 1; j >= 0; j--)
        answer[s.length() - j - 1] = s.charAt(j);
    return new String(answer);
}

答案 5 :(得分:0)

  

为什么第二个代码比第一个更有效?

String是不可改变的,通过answer += s.charAt(j);,您将在每个循环中创建String的新实例,这会使您的代码变慢。


为了提高性能和可读性,建议您在单线程上下文中使用StringBuilder,而不是使用String(可能比固定大小的char数组慢一点,但具有更好的可读性):

String reverse1(String s) {
  StringBuilder answer = new StringBuilder("");
  for (int j = s.length() - 1; j >= 0; j--) 
       answer.append(s.charAt(j)); 
 return answer.toString();
}

答案 6 :(得分:0)

JVM将字符串视为不可变的。因此,每次附加到现有字符串时,实际上就是在创建一个新字符串!这意味着必须为每次循环迭代在堆中创建一个新的字符串对象。创建对象并维持其生命周期有其开销。再加上被丢弃的字符串的垃圾回收(在上一个迭代中创建的字符串在下一个迭代中不会对其进行引用,因此,它是由JVM收集的。)

您应该考虑使用StringBuilder。我进行了一些测试,StringBuilder代码花费的时间并不比定长数组的时间小很多。

JVM处理字符串的方式有些细微差别。 JVM进行了诸如字符串实习之类的事情,因此不必为具有相同内容的多个字符串创建新对象。您可能需要调查一下。