1. 字符串的不可变性:朋友还是敌人?
在 Java 中,类 String 是不可变的(immutable)。这意味着创建之后就不能再更改该字符串。每次你“更改”字符串,例如通过 + 或 concat() 追加内容,实际发生的是创建了一个新的对象,而旧对象则等待垃圾回收。
示例:
String s = "Hello";
s = s + " world!";
System.out.println(s); // Hello world!
看起来好像字符串变量 s 被修改了,但实际上创建了一个新的字符串 "Hello world!",而旧的 "Hello" 会留在内存中,直到被垃圾回收器清理。如果这样的操作很多——例如在循环中——程序会开始变慢并占用额外的内存。
想象你在搭一座积木塔,每次想加一块新积木,都得把整座塔重搭一遍。不太经济,对吧?在频繁修改时,Java 中普通的 String 就是这么工作的。
2. StringBuilder:快速的“字符串构建器”
类 StringBuilder(包 java.lang,无需导入)是用于高效构建和修改字符串的专用工具。它是可变的(mutable):可以追加、删除、插入字符和子串,而无需为每次操作都创建新对象。
类比:
如果 String 是一块混凝土板,那么 StringBuilder 就是 LEGO 积木:你可以反复添加或移除零件,而不必把全部拆了重来。
如何创建 StringBuilder
StringBuilder sb = new StringBuilder(); // 空
StringBuilder sb2 = new StringBuilder("初始值");
主要方法
| 方法 | 说明 | 用法示例 |
|---|---|---|
|
在末尾追加字符串、数字、字符等。 | |
|
在指定位置插入值 | |
|
删除从位置 start(含)到 end(不含)的字符 | |
|
用其他内容替换字符串的一部分 | |
|
将字符串反转 | |
|
转换为普通字符串 | |
|
将长度裁剪或填充到指定值 | |
使用示例
StringBuilder sb = new StringBuilder();
sb.append("你好,");
sb.append("世界!");
System.out.println(sb); // 你好,世界!
sb.insert(7, "Java "); // 在“你好,”之后插入 "Java "
System.out.println(sb); // 你好,Java 世界!
sb.replace(8, 12, "其他"); // 将 "Java" 替换为 "其他"
System.out.println(sb); // 你好,其他世界!
sb.reverse();
System.out.println(sb); // !界世他其,好你
3. StringBuffer:带有线程安全的“老大哥”
StringBuilder 和 StringBuffer 有什么区别?
- StringBuilder:更快,但不是线程安全的(未同步)。
- StringBuffer:速度较慢,但线程安全(已同步)。
如果你的应用在单线程下工作——请使用 StringBuilder(更快)。如果多个线程可能修改同一段字符串——请使用 StringBuffer。
4. 何时使用 StringBuilder 而不是 String?
场景
- 在循环中或拼接大文本时对字符串进行频繁修改(追加、删除、插入)。
- 从数组/列表构建字符串(CSV、HTML、报告等)。
- 解析与文本处理,涉及大量字符串操作。
示例:从数组构建字符串
不推荐的方式(使用 String 和 +):
String[] names = {"伊万", "彼得", "玛丽娅"};
String result = "";
for (int i = 0; i < names.length; i++)
{
result += names[i];
if (i < names.length - 1)
{
result += ", ";
}
}
System.out.println(result);
推荐的方式(使用 StringBuilder):
String[] names = {"伊万", "彼得", "玛丽娅"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.length; i++)
{
sb.append(names[i]);
if (i < names.length - 1)
{
sb.append(", ");
}
}
System.out.println(sb.toString());
差异:第一种方式每一步都会创建一个新字符串,第二种方式则是在同一个 StringBuilder 对象上累积构建。
5. 性能对比:String vs StringBuilder
// 通过 String
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++)
{
s += i + " ";
}
long t2 = System.currentTimeMillis();
System.out.println("String: " + (t2 - t1) + " 毫秒");
// 通过 StringBuilder
t1 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.append(i).append(" ");
}
s = sb.toString();
t2 = System.currentTimeMillis();
System.out.println("StringBuilder: " + (t2 - t1) + " 毫秒");
结论:在大多数情况下,进行多次连接时,StringBuilder 的速度要快得多。
6. 实用细节
能用 String 的方法吗?
StringBuilder 有自己的方法。若要得到字符串——请调用 toString()。
StringBuilder sb = new StringBuilder("Hello");
String s = sb.toString(); // 现在 s 是普通字符串
能用 equals 比较 StringBuilder 吗?
注意:sb1.equals(sb2) 比较的是引用而非内容。请这样比较:
if (sb1.toString().equals(sb2.toString()))
{
// 内容相同
}
可以把 StringBuilder 传给 System.out.println 吗?
可以。System.out.println 会自动调用 toString()。
可以按索引读取字符吗?
可以,使用 charAt(int index),与普通字符串相同。
7. 使用 StringBuilder 和 StringBuffer 时的常见错误
错误 1:用 equals 或运算符 == 比较两个 StringBuilder。这些检查比较的是引用而非内容。请使用 toString() 后再比较字符串。
错误 2:在需要 String 的地方(从方法返回、日志记录、传给 API)忘记调用 toString()。
错误 3:为两三次简单的连接就使用 StringBuilder。像 "Hello, " + name 这样的写法可读且高效。
错误 4:在循环中通过 + 连接 String。这在时间和内存上都不高效——请使用 StringBuilder。
错误 5:在没有必要时混淆 StringBuffer 和 StringBuilder。如果没有来自多个线程的并发修改——请选择 StringBuilder。
错误 6:不加思考地用 setLength() 裁剪 StringBuilder 的内容。请确保新长度正确,超过该长度的数据将被丢弃。
GO TO FULL VERSION