【Java】字符串拼接完全指南:从原理到实战优化
大家好,我是云扬~ 字符串拼接是 Java 开发中最常用的操作之一,但很多同学可能没意识到,不同拼接方式的性能差异和底层实现天差地别。今天就带大家深入拆解 5 种常用的字符串拼接方法,结合源码和实战场景,帮你避开坑点、选对方案!
一、+ 号操作符:语法糖的甜蜜与陷阱
我们最熟悉的+号拼接,本质是 Java 编译器提供的语法糖。先看一个直观示例:
// 原代码
String a = "Hello";
String b = "World";
String c = a + b;
// 编译后实际执行(反编译class文件)
String a = "Hello";
String b = "World";
String c = new StringBuilder().append(a).append(b).toString();
关键特性:
- 编译期优化:如果拼接的是编译时常量,会直接优化为常量字符串:java
String d = 11 + ""; // 编译后直接是 String d = "11"; - 循环中的致命问题:循环体内使用
+号会创建大量StringBuilder对象,导致内存浪费和 GC 频繁:
// 反面示例:循环中滥用+号(性能极差)
String result = "";
for (int i = 0; i<1000; i++) {
result += i; // 每次循环都新建StringBuilder
}
二、StringBuilder.append:循环拼接的最优解
既然+号在循环中会创建多个StringBuilder,那直接手动控制StringBuilder实例即可:
正确写法:
// 正面示例:循环外创建StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i< 1000; i++) {
sb.append(i); // 复用同一个对象,性能拉满
}
String result = sb.toString();
源码核心逻辑(AbstractStringBuilder):
// 简化核心流程
public AbstractStringBuilder append(String str) {
if (str == null) return appendNull(); // null处理为"null"
int len = str.length();
ensureCapacityInternal(count + len); // 数组扩容检查
str.getChars(0, len, value, count); // 字符数组复制
count += len;
return this;
}
关键优化点:
- 初始容量:默认 16 字符,可指定初始容量减少扩容次数:
new StringBuilder(1024) - 线程不安全:单线程场景使用,多线程用
StringBuffer(效率较低)
三、String.concat:简单拼接的轻量选择
concat()方法是 String 类原生的拼接方式,语法简洁:
使用示例:
String a = "Hello";
String b = "World";
String c = a.concat(b); // 结果:"HelloWorld"
源码核心特性:
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);
}
- 空字符串优化:拼接空字符串时直接返回原对象,无需创建新字符串
- null 处理:遇到 null 会抛出
NullPointerException(与+号的 “null” 处理不同):
// 对比测试
String e = null + "test"; // 结果:"nulltest"
String f = "test".concat(null); // 抛出NPE
3. 适用场景:仅需拼接 1-2 个字符串,且确定无 null 的场景
四、String.join:带分隔符的优雅拼接
Java 8 新增的String.join()静态方法,专为多字符串拼接 + 分隔符设计:
基本用法:
// 分隔符 + 可变参数
String names = String.join(",", "张三", "李四", "王五");
System.out.println(names); // 输出:"张三,李四,王五"
// 分隔符 + 集合
List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子");
String fruitStr = String.join("|", fruits);
System.out.println(fruitStr); // 输出:"苹果|香蕉|橙子"
底层实现:
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
内部通过StringJoiner实现,自动处理分隔符,避免手动拼接分隔符的冗余代码。
五、StringUtils.join:空安全的实用工具
来自 org.apache.commons.lang3的StringUtils.join(),解决了 null 处理的痛点:
核心优势:
// 自动忽略null,无需手动判空
String[] arr = {"Java", null, "Python", "Go"};
String result = StringUtils.join(arr, "-");
System.out.println(result); // 输出:"Java-Python-Go"
// 集合拼接同样支持
List<String> list = Arrays.asList("Spring", null, "MyBatis");
String listResult = StringUtils.join(list, ",");
底层逻辑:
public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
if (array == null) {
return null;
}
if (separator == null) {
separator = EMPTY;
}
final StringBuilder buf = new StringBuilder(noOfItems * 16);
for (int i = startIndex; i < endIndex; i++) {
if (i > startIndex) {
buf.append(separator);
}
if (array[i] != null) {
buf.append(array[i]);
}
}
return buf.toString();
}
内部仍使用StringBuilder实现,增加了 null 过滤逻辑,开发效率和性能兼顾。
六、方法对比与场景选择
| 拼接方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| + 号操作符 | 语法简洁 | 循环中性能差 | 少量字符串拼接 |
| StringBuilder.append | 性能最优 | 语法稍繁琐 | 循环拼接、大量字符串 |
| String.concat | 轻量无额外依赖 | 不支持 null、多字符串拼接麻烦 | 1-2 个非 null 字符串拼接 |
| String.join | 分隔符优雅处理 | 不支持 null(需手动过滤) | 带分隔符的多字符串拼接 |
| StringUtils.join | 空安全、功能全面 | 需引入第三方依赖 | 项目已依赖 Commons Lang3 |
总结
- 日常开发中,循环拼接优先用
StringBuilder.append,记得指定初始容量 - 少量字符串拼接用
+号即可,代码简洁易读 - 带分隔符的拼接优先用
String.join,干净优雅 - 涉及 null 的场景直接用
StringUtils.join,避免空指针踩坑
字符串拼接看似简单,实则藏着不少优化细节。选择合适的拼接方式,既能提升代码性能,也能让代码更具可读性。你在项目中常用哪种拼接方式?欢迎在评论区交流~



