提高Java中字符串连接的性能[重复]
Posted
技术标签:
【中文标题】提高Java中字符串连接的性能[重复]【英文标题】:Improving performance of string concatenation in Java [duplicate] 【发布时间】:2011-02-15 17:46:14 【问题描述】:可能重复:java String concatenation
如何提高这段代码的性能:
public static String concatStrings(Vector strings)
String returnValue = "";
Iterator iter = strings.iterator();
while( iter.hasNext() )
returnValue += (String)iter.next();
return returnValue;
【问题讨论】:
这是作业吗?这本身并没有什么问题,它应该被标记为这样。 ***.com/questions/47605/java-string-concatenation 看起来不像一个典型的家庭作业问题。如果涉及性能,当然不是。学生通常没有意识到这一点,或者没有比较材料;) 不要使用原始类型,你可能不需要Vector
的同步。
与 future-maintenance-performance 相关 - 您的方法可以采用 Iterable<String>
代替。
【参考方案1】:
您可能会考虑使用StringBuilder,而不是对单个字符串使用+=。字符串在 Java 中是不可变的,这意味着一旦创建了 String 对象,就无法修改它。在循环中对字符串使用 += 将导致创建许多单独的 String 实例,这可能会产生性能问题。 StringBuilder 可以连接字符串,而无需创建新实例,这可能会节省一些时间,具体取决于具体场景。
【讨论】:
+=
每次都会隐式创建一个 new 字符串。
你能把你的解释写成代码吗?
循环前创建StringBuilder
,循环内调用append()
方法,循环后使用toString()
得到结果。
我不能把它放在代码中,但它很容易。只需创建一个新的 StringBuilder,并在循环中使用“append”方法将新字符串添加到缓冲区。完成后,只需调用 stringBuilder.toString() 即可获得最终结果。【参考方案2】:
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.Vector
是 synchronized
;如果您不需要这个(昂贵的)功能,只需使用ArrayList
。
不要使用原始类型
JLS 4.8 Raw Types
仅允许使用原始类型作为对遗留代码兼容性的让步。强烈反对在将泛型引入 Java 编程语言之后编写的代码中使用原始类型。 Java 编程语言的未来版本可能会禁止使用原始类型。
有效的 Java 第 2 版:第 23 条:不要在新代码中使用原始类型
如果您使用原始类型,您将失去泛型的所有安全性和表现力优势。
另见
Java string concatenation Java Tutorials - Generics【讨论】:
另见***.com/questions/2770321/…【参考方案3】:正如其他答案所建议的那样,使用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();
【讨论】:
+1 因为编译器会在某些情况下优化连接,即“+”并不总是邪恶的。但是,在问题中提出的那种情况下,开销不是创建新的 StringBuilder 实例(对象分配通常非常快),而是在返回新 String 之前将 StringBuilder 缓冲区复制到 String (String guarantees immutability so they can't重复使用相同的缓冲区) @CurtainDog - IIRC,StringBuilder.toString()
在创建结果String
时巧妙地避免了复制字符数组。看一下代码。
@Stephen C:最新版本绝对不是这样。一般来说,Java 标准库会非常小心地避免任何可能的情况,即字符串可能会被意外变异。见google.com/codesearch/p?hl=en#gbgUBv1WUj4/src/share/classes/…
@David - 我今天学到了一些新东西!显然变化发生在 1.4.x 和 1.5 之间……bugs.sun.com/bugdatabase/view_bug.do?bug_id=6219959
@Stephen C - 谢谢,你让我觉得自己很年轻……我所有严肃的 Java 编码都必须在 1.5 之后出现。当然,我发现我最初的评论是不准确的,因为“无法重用”改为“发现重用有问题”【参考方案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。
Java 文章的绳索 - http://www.ibm.com/developerworks/java/library/j-ropes Java 实现的绳索 - http://ahmadsoft.org/ropes/ 绳索(***)-http://en.wikipedia.org/wiki/Rope_(computer_science)【讨论】:
【参考方案5】:此外,如果您希望加快速度,您可以重构代码以使用 ArrayList 而不是 Vector。 ArrayList 不是线程安全的,所以比 Vector 稍快(视情况而定,可能是 0% 的差异,也可能是 5% 的差异)。
【讨论】:
【参考方案6】:每次调用 += 时都会创建一个字符串。例如
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 进行大量附加操作,您可以想象加起来的效果!
【讨论】:
【参考方案7】:除了使用 StringBuilder 之外,您还可以预先遍历 String 列表并计算 StringBuilder 所需的确切大小。然后将此值传递给 StringBuilder 构造函数。请注意,这将属于过早优化的类别,但您确实要求性能...(您应该查看用于增加 StringBuilder/StringBuffer 缓冲区的代码,其教育意义)
【讨论】:
【参考方案8】:除了使用 ArrayList 和 StringBuilder 之外,让我们考虑一下。
在现代计算机科学范式中,空间几乎总是可以换取时间(也许,这是一种主观陈述)。对于给定的场景,使用下面的代码,使用了 O(N) 的额外空间,其中 N = 没有字符串(对于保存 list.toArray() 的新缓冲区)。这至少比使用 Iterator 好(打开 AbstractList.iterator())。重要的是,通过在一次迭代中一次计算两个字符串的连接,时间复杂度明显更好,从而将迭代次数减少了一半!这有点像使用动态编程方法(记住,使用动态编程计算斐波那契数)!!
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]);
【讨论】:
以上是关于提高Java中字符串连接的性能[重复]的主要内容,如果未能解决你的问题,请参考以下文章