字符串拼接暗战
2025.03.18五大拼接方案的技术对比
先说结论,如果在处理大量字符串的时候就需要注意性能和耗时,以下是耗时比较(短->长):StringBuilder
<StringBuffer
<StringUtils.join
<concat
<+
。
方法名称 | 特点 | 适用场景 | 注意事项 |
---|---|---|---|
StringBuilder |
非线程安全/动态扩容/内存预分配 | 循环体/高频拼接/超大文本处理 | 多线程场景禁用 |
StringBuffer |
线程安全/同步锁/性能损耗 | 多线程并发拼接 | 非并发场景性能浪费 |
String.concat() |
原生方法/仅限两个字符串连接 | 简单双字符串合并 | 连续调用产生中间对象 |
+ 操作符 |
语法糖/编译优化 | 静态拼接/少量动态拼接 | 循环内使用触发性能灾难 |
StringUtils.join |
空值处理/依赖第三方库 | 复杂集合拼接/已有项目集成 | 引入额外Jar包依赖 |
因为String
类是不可变的,所以所谓字符串拼接,本质都是重新生成一个新的字符串。以下是字符串的几种拼接的方式详细说明。
方案一:原生concat方法
使用concat
方法连接字符串:
String str2 = "123".concat("456");
System.out.println(str2);
作为String
类自带的拼接方法,concat()
直接操作JVM底层字符数组实现两个字符串的合并。
其源码通过数组拷贝完成操作:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) return this;
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
该方法适用于简单双字符串合并场景,但连续调用会产生中间对象,需谨慎处理高频拼接操作。
方案二:运算符"+“连接字符串
+运算符的语法糖特性使其在编译阶段触发智能优化。对于静态字符串拼接:
String s = "Java" + "实战"; // 编译优化为"Java实战"
编译器直接合并字符串常量。但当存在变量时:
String s1 = "Java";
String s2 = s1 + "实战"; // 转换为StringBuilder操作
此时编译器自动生成StringBuilder
处理流程,但循环场景下会产生重复对象。
方案三:StringBuilder/StringBuffer连接字符串
StringBuilder
(非线程安全)与StringBuffer
(线程安全)采用动态数组策略,初始容量16并支持自动扩容(新容量=原容量*2+2)。两者的核心差异体现在同步机制。
StringBuffer
关键方法实现:
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuilder
则无同步锁机制,这使得单线程场景下其性能比StringBuffer
提升约15%-20%。
方案四:StringUtils.join连接字符串
Java8引入的String.join
方法统一了集合与数组的拼接处理:
String[] arr = {"A", "B", "C"};
List<String> list = Arrays.asList(arr);
String s3 = String.join("|", arr); // A|B|C
String s4 = String.join("-", list); // A-B-C
该方法内部使用StringJoiner实现,适合处理预定义分隔符的集合拼接场景。
方案五:第三方工具扩展
例如,apache.commons
。StringUtils
是Apache Common Lang库中的一个工具类,提供了许多字符串处理的方法,包括join
方法,用于将数组或集合以指定的分隔符拼接起来。
// Java8提供的String.join方法和`apache.commons`方法相似.
// 主要作用是将数组或集合以某拼接符拼接到一起形成新的字符串
String str9 = StringUtils.join(new String[]{str, "456", "789"});
该工具类具备空值处理能力,但需要引入外部依赖包。
底层机制深度解密
深入解析“+”拼接原理
拼接字符串最简单的方式就是直接使用符号”+“来拼接,其实“+”是Java提供的一个语法糖。 语法糖,也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。 语法糖让程序更加简洁,有更高的可读性。
为了探究“+”拼接的原理,用javap -verbose
命令进行反编译看一下:
public static void main(String[] args) {
String str1 = "123";
String str2 = "456";
String str3 = str1 + str2;
System.out.println(str3);
}
// ...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: ldc #2 // String 123
2: astore_1
3: ldc #3 // String 456
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
32: return
// ...
可以看到在字符串拼接过程中,是将String
转成了StringBuilder
后,使用append
方法进行拼接字符串处理的,最后在去调用StringBuilder
的toString
方法进行返回。
在JDK5之后,使用的是StringBuilder
,在JDK5之前使用的是StringBuffer
。
知道了“+”底层之后,如果在循环中,使用“+”拼接字符串,会创建大量的StringBuilder
对象,造成内存的浪费。
所以对于大量字符串拼接操作,推荐使用StringBuilder
或StringBuffer
以提高性能。
// 不推荐
public class StringConcatExample {
public static void main(String[] args) {
String result = "";
for (int i = 0; i < 10000; i++) {
result += i;
}
System.out.println(result.length());
}
}
// 推荐
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < 10000; i++) {
result.append(i);
}
System.out.println(result.length());
}
}
StringBuffer与StringBuilder深度解密
Stringbuilder
和StringBuffer
是字符串拼接的两种实现类,它们有相同的父类:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
// ...
}
StringBuffer
和StringBuilder
是 Java 提供的用于处理字符串的类,二者的主要区别在于线程安全性和性能方面。拿append
方法举例:
StringBuilder.append
方法@Override public StringBuilder append(String str) { super.append(str); return this; }
StringBuffer.append
方法@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
由此可以看出StringBuilder
和StringBuffer
原理是相似的,最大的区别就是StringBuffer
是线程安全的,原因是用了synchronized
修饰。
StringBuffer
是线程安全的,因为它的方法都是同步的,这意味着它是安全的,可以在多线程环境中使用。
由于其线程安全性,每个方法调用都有同步开销,所以StringBuffer
的性能比StringBuilder
稍慢。
与String
类不同的是,StringBuffer
和StringBuilder
类的对象能够被多次的修改,并且不产生新的未使用对象。
如果你需要一个可修改的字符串,应该使用StringBuffer
或者StringBuilder
,但是会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的String
对象被创建出来。
通过StringBuilder
的append()
方式添加字符串的效率,要远远高于String
的字符串拼接方法。
在实际开发中还可以进行优化,StringBuilder
的空参构造器,默认的字符串容量是16,如果需要存放的数据过多,容量就会进行扩容,我们可以设置默认初始化更大的长度,来减少扩容的次数。
如果我们能够确定,前前后后需要添加的字符串不高于某个限定值,那么建议使用构造器创建一个阈值的长度。