Java字符串与StringBuilder详解
1. String类的特性
不可变性(Immutability)
public class StringDemo {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = str1 + " World"; // 创建了新对象
System.out.println(str1); // 输出: Hello
System.out.println(str2); // 输出: Hello World
// 验证不可变性
String original = "Java";
String modified = original.concat(" Programming");
System.out.println("Original: " + original); // 输出: Java
System.out.println("Modified: " + modified); // 输出: Java Programming
}
}
String拼接的内存问题
public class StringConcatenation {
public static void main(String[] args) {
String result = "";
// 每次循环都会创建新的String对象
for (int i = 0; i < 5; i++) {
result += "Number " + i + " "; // 低效的拼接方式
// 等价于: result = new StringBuilder().append(result).append("Number ").append(i).append(" ").toString();
}
System.out.println(result);
}
}
2. StringBuilder详解
可变性(Mutability)
public class StringBuilderDemo {
public static void main(String[] args) {
// 创建StringBuilder实例
StringBuilder sb = new StringBuilder();
// 追加内容 - 不会创建新对象
sb.append("Hello");
sb.append(" ");
sb.append("World");
// 转换为String
String result = sb.toString();
System.out.println(result); // 输出: Hello World
// 其他常用方法
sb.insert(5, " Beautiful"); // 在指定位置插入
System.out.println(sb.toString()); // 输出: Hello Beautiful World
sb.delete(5, 15); // 删除指定范围
System.out.println(sb.toString()); // 输出: Hello World
sb.reverse(); // 反转字符串
System.out.println(sb.toString()); // 输出: dlroW olleH
}
}
3. 性能对比:数组拼接示例
使用String拼接数组(低效)
public class StringArrayConcatenation {
public static String concatenateWithString(int[] array) {
String result = "";
for (int i = 0; i < array.length; i++) {
result += array[i];
if (i < array.length - 1) {
result += ", ";
}
}
return result;
}
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
long startTime = System.currentTimeMillis();
String result = concatenateWithString(numbers);
long endTime = System.currentTimeMillis();
System.out.println("结果: " + result);
System.out.println("String拼接耗时: " + (endTime - startTime) + "ms");
}
}
使用StringBuilder拼接数组(高效)
public class StringBuilderArrayConcatenation {
public static String concatenateWithStringBuilder(int[] array) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; i++) {
sb.append(array[i]);
if (i < array.length - 1) {
sb.append(", ");
}
}
return sb.toString();
}
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
long startTime = System.currentTimeMillis();
String result = concatenateWithStringBuilder(numbers);
long endTime = System.currentTimeMillis();
System.out.println("结果: " + result);
System.out.println("StringBuilder拼接耗时: " + (endTime - startTime) + "ms");
}
}
4. 内存占用分析
String拼接的内存消耗过程
public class MemoryAnalysis {
public static void analyzeStringMemory() {
String[] words = {"A", "B", "C", "D", "E"};
String result = "";
System.out.println("String拼接过程的内存对象创建:");
for (int i = 0; i < words.length; i++) {
result += words[i];
System.out.println("第" + (i+1) + "次拼接后: " + result);
// 每次循环都会创建新的String对象
}
}
public static void analyzeStringBuilderMemory() {
String[] words = {"A", "B", "C", "D", "E"};
StringBuilder sb = new StringBuilder();
System.out.println("\nStringBuilder拼接过程:");
for (int i = 0; i < words.length; i++) {
sb.append(words[i]);
System.out.println("第" + (i+1) + "次追加后: " + sb.toString());
// 始终使用同一个StringBuilder对象
}
}
public static void main(String[] args) {
analyzeStringMemory();
analyzeStringBuilderMemory();
}
}
5. 为什么StringBuilder更节省内存
详细的内存占用对比
public class DetailedMemoryComparison {
// 使用String拼接 - 产生多个中间对象
public static void stringConcatenationMemory() {
int[] array = {1, 2, 3, 4, 5};
String result = "";
// 内存分析:
// 循环1: "" + "1" → 创建新String "1"
// 循环2: "1" + "2" → 创建新String "12"
// 循环3: "12" + "3" → 创建新String "123"
// 循环4: "123" + "4" → 创建新String "1234"
// 循环5: "1234" + "5" → 创建新String "12345"
// 总共创建了5个String对象!
for (int num : array) {
result += num;
}
}
// 使用StringBuilder - 只有一个对象
public static void stringBuilderConcatenationMemory() {
int[] array = {1, 2, 3, 4, 5};
StringBuilder sb = new StringBuilder();
// 内存分析:
// 只创建一个StringBuilder对象
// 所有append操作都在同一个对象内部进行
// 最后只创建一个String对象
for (int num : array) {
sb.append(num);
}
String result = sb.toString();
}
}
6. StringBuilder的内部机制
容量自动扩展
public class StringBuilderInternal {
public static void showCapacityGrowth() {
StringBuilder sb = new StringBuilder(); // 初始容量16
System.out.println("初始容量: " + sb.capacity());
for (int i = 0; i < 50; i++) {
sb.append("x");
if (i == 15 || i == 34 || i == 49) {
System.out.println("添加 " + (i+1) + " 个字符后容量: " + sb.capacity());
}
}
}
public static void main(String[] args) {
showCapacityGrowth();
/*
输出示例:
初始容量: 16
添加 16 个字符后容量: 34 (16 * 2 + 2)
添加 35 个字符后容量: 70 (34 * 2 + 2)
添加 50 个字符后容量: 70
*/
}
}
7. 最佳实践建议
选择合适的场景
public class BestPractices {
// 场景1: 简单的字符串连接 - 使用String
public static String simpleConcatenation() {
String firstName = "John";
String lastName = "Doe";
return firstName + " " + lastName; // 编译器会优化为StringBuilder
}
// 场景2: 循环中的字符串连接 - 使用StringBuilder
public static String buildCSV(int[] data) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i++) {
sb.append(data[i]);
if (i < data.length - 1) {
sb.append(",");
}
}
return sb.toString();
}
// 场景3: 预知大致长度 - 设置初始容量
public static String buildLargeString(String[] words) {
// 预估最终字符串长度,设置合适的初始容量
int estimatedLength = words.length * 10; // 假设每个单词平均10字符
StringBuilder sb = new StringBuilder(estimatedLength);
for (String word : words) {
sb.append(word).append(" ");
}
return sb.toString().trim();
}
// 场景4: 链式调用
public static String chainedAppend() {
return new StringBuilder()
.append("Name: ").append("John")
.append(", Age: ").append(25)
.append(", Score: ").append(95.5)
.toString();
}
}
总结
StringBuilder节省内存的关键原因:
-
对象创建数量:
- String拼接:每次拼接都创建新String对象
- StringBuilder:只创建一个对象
-
内存分配策略:
- String:每次都需要分配新内存
- StringBuilder:预分配容量,自动扩展,减少内存重新分配
-
垃圾回收压力:
- String:产生大量中间对象,增加GC负担
- StringBuilder:对象数量最少,GC压力小
-
性能影响:
- 在循环或大量拼接场景中,StringBuilder的性能优势非常明显
使用建议:
- 简单拼接:使用
+
操作符 - 循环或复杂拼接:使用
StringBuilder
- 多线程环境:使用
StringBuffer
- 预知长度时:设置合适的初始容量