提高Java中字符串连接的性能

时间:2010-05-05 01:45:28

标签: java performance

  

可能重复:
  java String concatenation

如何提高此代码块的性能:

public static String concatStrings(Vector strings) {
    String returnValue = "";

    Iterator iter = strings.iterator();
    while( iter.hasNext() ) {
        returnValue += (String)iter.next();
    }

    return returnValue;
}

8 个答案:

答案 0 :(得分:14)

您可能会考虑使用StringBuilder,而不是使用单个字符串执行+ =。字符串在Java中是不可变的,这意味着一旦创建了String对象,就无法对其进行修改。在循环中对字符串使用+ =将导致创建许多单独的String实例,这可能会产生性能问题。 StringBuilder可以连接字符串而无需创建新实例,这可能会节省一些时间,具体取决于具体情况。

答案 1 :(得分:8)

public static String concatStrings(List<String> strings) {
    StringBuilder sb = new StringBuilder();
    for (String s : strings) {
       sb.append(s);
    }    
    return sb.toString();
}

一些评论:

  • 每当需要在循环中构建字符串时,请使用StringBuilder
    • +适用于简单连接,但增量构建很糟糕
  • 尽可能使用for-each以提高可读性
  • java.util.Vectorsynchronized;如果您不需要这个(昂贵的)功能,只需使用ArrayList

不要使用原始类型

  • JLS 4.8 Raw Types

      

    原始类型的使用仅允许作为遗留代码兼容性的让步。强烈建议不要在将通用性引入Java编程语言之后编写的代码中使用原始类型。 未来版本的Java编程语言可能会禁止使用原始类型。

  • Effective Java 2nd Edition:第23项:不要在新代码中使用原始类型

      

    如果您使用原始类型,则会失去仿制药的所有安全性和表现力。

另见

答案 2 :(得分:6)

正如其他答案所示,使用StringBuilder可能是更好的选择。

问题中给出的代码实际上将被编译(使用Sun的javac)到以下某行:

public static String concatStrings(Vector strings) {
    String returnValue = "";

    Iterator iter = strings.iterator();
    while( iter.hasNext() ) {
        String str = (String)iter.next();

        StringBuilder sb = new StringBuilder(returnValue);
        sb.append(str);

        returnValue = sb.toString();
    }

    return returnValue;
}

编译器会将+=字符串连接更改为使用StringBuilder的字符串连接。但是,编译器可能会重写循环内的代码,因此将在每次迭代上创建一个新的StringBuilder实例,这对性能不是很友好。

因此,在这种情况下,最好在我们自己的循环外创建StringBuilder,并执行手动字符串连接:

public static String concatStrings(Vector strings) {
    StringBuidler returnValueBuilder;

    Iterator iter = strings.iterator();
    while( iter.hasNext() ) {
        returnValueBuilder.append((String)iter.next());
    }

    return returnValueBuilder.toString();
}

答案 3 :(得分:4)

private static final int AVERAGE_STRING_LENGTH = 10;  // Using 10 is arbitrary

public static final String concatStrings(final Collection<String> strings) {
    if (strings == null)   return null;

    final int size = strings.size();
    if (size == 0)         return "";
    if (size == 1)         return strings.get(0);

    final StringBuilder returnValue =
        new StringBuilder(AVERAGE_STRING_LENGTH * size);

    for (String s : strings) {
        returnValue.append(s);
    }

    return returnValue.toString();
}

也许有点过分,这里的我能想到的concatStrings()的每个优化 - 如上所示 - 其中一些可能不适用于您的环境:

  • 使用StringBuilder - 对于这些连续的连接来说效率更高
  • 使用StringBuilder(int capacity)指定可能的必要容量,如果有任何方法可以预测它(使用上面的平均大小,但其他方法可能更方便)
  • 使用Collection参数类型可以提供比同步的Vector更高效的数据结构 - 加上调用者具有更大的灵活性(例如,无需复制Set<String>Vector<String>只是为了调用这种方法)
  • 硬编码简单案例,如果可能的话(例如null,尺寸0和尺寸1以上情况
  • 使用final促进JIT内联和优化
  • 如果多次使用,则缓存strings的大小。 (例如,在上面的代码中使用了3次。)

最后,如果此操作经常在大量字符串上完成,请查看Ropes for Java

答案 4 :(得分:1)

此外,如果您希望加快速度,可以重构代码以使用ArrayList而不是Vector。 ArrayList不是线程安全的,所以它比Vector稍快(取决于情况,可能是0%的差异,可能是5%的差异)。

答案 5 :(得分:1)

每次调用+ =时都会创建一个字符串。例如

String theString = "1"; //Makes an immutable String object "1"
theString +="2"; //Makes a new immutable String object "12"
theString +="3"; //makes a new immutable String object "123"

使用字符串生成器可以避免此问题。

StringBuilder sb = new StringBuilder("1"); //Makes a StringBuilder object holding 1
sb.append("2"); //The same StringBuilder object now has "12" in it.
sb.append("3"); //The same StringBuidler object now has "123" in it. 
String theString = sb.toString(); //Creates a new String object with "123" in it 

注意在第一个例子中我们如何制作所有这些中间字符串,在第二个例子中我们只创建了StringBuilder和最终字符串(在我们使用它们的两个例子中,我们创建了“1”“2”和“3”作为论点)。您可以看到在第一个示例中创建的对象较少,如果您正在对String进行大量附加,您可以想象它是如何累加的!

答案 6 :(得分:1)

除了使用StringBuilder之外,您还可以预先遍历字符串列表并计算StringBuilder所需的确切大小。然后将此值传递给StringBuilder构造函数。请注意,这将属于过早优化的类别,但您确实要求性能......(您应该查看用于增长StringBuilder / StringBuffer缓冲区的代码,其教育性)

答案 7 :(得分:0)

除了使用ArrayList和StringBuilder之外,让我们考虑一下。

在现代计算机科学范式中,空间几乎总是可以随时间交换(也许,这是一种主观陈述)。对于给定的场景,使用下面的代码,使用额外的O(N)空间,其中N =没有字符串(对于包含list.toArray()的新缓冲区)。这比至少使用Iterator更好,(打开AbstractList.iterator())。重要的是,通过在一次迭代中一次计算两个字符串的串联,时间复杂度明显更好,从而将迭代次数减少一半!这类似于使用动态编程方法(记住,使用动态编程计算Fibonacci nos !!)

    StringBuilder sb = new StringBuilder();
    Object[] o = list.toArray();
    //For even no of Strings
    if(o.length % 2 == 0){
        concatFaster(sb, o);
    } else {
        //For odd no of Strings
        concatFaster(sb, o);
        sb.append(o[o.length-1]); // For the odd index
    }

    public static void concatFaster(StringBuilder sb, Object[] o) {
    for (int i = 0; i < o.length - 1; i+=2) {
        sb.append(o[i]).append(o[i+1]);
    }
}