在Java编程中,字符串处理是日常开发中最常见的任务之一。由于String类的不可变性,频繁的字符串拼接操作会产生大量的临时对象,影响程序性能。为了解决这个问题,Java提供了StringBufferStringBuilder两个可变的字符串类。本文将重点介绍StringBuffer的常用方法,并通过实战案例分享其应用技巧。

一、StringBuffer与String、StringBuilder的区别

在深入学习StringBuffer之前,我们先明确它与其他字符串类的区别:

特性 String StringBuffer StringBuilder
可变性 不可变 可变 可变
线程安全 线程安全(因为不可变) 线程安全(同步方法) 非线程安全
性能 拼接性能差 拼接性能较好(有同步开销) 拼接性能最好
适用场景 字符串常量、少量操作 多线程环境下的字符串操作 单线程环境下的字符串操作

关键点StringBuffer的所有公开方法都是synchronized修饰的,这意味着它在多线程环境下是安全的,但会带来一定的性能开销。在单线程环境下,推荐使用StringBuilder以获得更好的性能。

二、StringBuffer常用方法详解

1. 构造方法

// 1. 空构造器,创建一个空的StringBuffer
StringBuffer sb1 = new StringBuffer();

// 2. 指定初始容量的构造器(推荐)
StringBuffer sb2 = new StringBuffer(1024); // 预分配1024字符空间

// 3. 用字符串初始化
StringBuffer sb3 = new StringBuffer("Hello");

实战技巧:如果预估字符串长度,建议使用带容量参数的构造器,避免频繁扩容。例如,处理JSON数据时,可以预估大小:

// 预估JSON字符串长度,避免频繁扩容
StringBuffer jsonBuffer = new StringBuffer(2048);

2. 基本操作方法

append()方法

StringBuffer sb = new StringBuffer();

// 追加各种类型的数据
sb.append("Hello");      // 追加字符串
sb.append(' ');          // 追加字符
sb.append(123);          // 追加整数
sb.append(3.14);         // 追加浮点数
sb.append(true);         // 追加布尔值
sb.append(new Object()); // 追加对象(调用toString())

System.out.println(sb.toString()); // 输出: Hello 1233.14truejava.lang.Object@...

实战案例:动态构建SQL查询语句

public String buildDynamicQuery(String table, String condition, String orderBy) {
    StringBuffer sql = new StringBuffer(256);
    sql.append("SELECT * FROM ").append(table);
    
    if (condition != null && !condition.isEmpty()) {
        sql.append(" WHERE ").append(condition);
    }
    
    if (orderBy != null && !orderBy.isEmpty()) {
        sql.append(" ORDER BY ").append(orderBy);
    }
    
    return sql.toString();
}

// 使用示例
String query = buildDynamicQuery("users", "age > 18", "name ASC");
System.out.println(query); // SELECT * FROM users WHERE age > 18 ORDER BY name ASC

insert()方法

StringBuffer sb = new StringBuffer("World");

// 在指定位置插入内容
sb.insert(0, "Hello ");  // 在开头插入
sb.insert(6, "beautiful "); // 在第6个位置插入
sb.insert(sb.length(), "!"); // 在末尾插入

System.out.println(sb.toString()); // 输出: Hello beautiful World!

实战案例:格式化日期时间

public String formatDateTime(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    
    StringBuffer sb = new StringBuffer(20);
    sb.append(cal.get(Calendar.YEAR)).append("-")
      .append(String.format("%02d", cal.get(Calendar.MONTH) + 1)).append("-")
      .append(String.format("%02d", cal.get(Calendar.DAY_OF_MONTH))).append(" ")
      .append(String.format("%02d", cal.get(Calendar.HOUR_OF_DAY))).append(":")
      .append(String.format("%02d", cal.get(Calendar.MINUTE))).append(":")
      .append(String.format("%02d", cal.get(Calendar.SECOND)));
    
    return sb.toString();
}

// 使用示例
Date now = new Date();
System.out.println(formatDateTime(now)); // 输出: 2023-10-15 14:30:25

delete()和deleteCharAt()方法

StringBuffer sb = new StringBuffer("Hello World");

// 删除指定范围的字符(左闭右开)
sb.delete(5, 6); // 删除第5个字符(空格)
System.out.println(sb.toString()); // 输出: HelloWorld

// 删除指定位置的字符
sb.deleteCharAt(5); // 删除第5个字符('o')
System.out.println(sb.toString()); // 输出: HellWorld

实战案例:清理HTML标签

public String removeHtmlTags(String html) {
    if (html == null || html.isEmpty()) {
        return "";
    }
    
    StringBuffer sb = new StringBuffer(html);
    int start = sb.indexOf("<");
    int end = sb.indexOf(">");
    
    while (start != -1 && end != -1 && start < end) {
        sb.delete(start, end + 1);
        start = sb.indexOf("<");
        end = sb.indexOf(">");
    }
    
    return sb.toString();
}

// 使用示例
String html = "<div>Hello <b>World</b></div>";
String plainText = removeHtmlTags(html);
System.out.println(plainText); // 输出: Hello World

replace()方法

StringBuffer sb = new StringBuffer("Hello Java");

// 替换指定范围的字符
sb.replace(6, 10, "Python"); // 替换第6到第9个字符
System.out.println(sb.toString()); // 输出: Hello Python

// 替换所有匹配的字符串(需要自己实现)
public static void replaceAll(StringBuffer sb, String oldStr, String newStr) {
    int index = sb.indexOf(oldStr);
    while (index != -1) {
        sb.replace(index, index + oldStr.length(), newStr);
        index = sb.indexOf(oldStr, index + newStr.length());
    }
}

// 使用示例
StringBuffer text = new StringBuffer("apple banana apple");
replaceAll(text, "apple", "orange");
System.out.println(text.toString()); // 输出: orange banana orange

3. 查询与获取方法

length()和capacity()方法

StringBuffer sb = new StringBuffer(100);
sb.append("Hello");

System.out.println("当前长度: " + sb.length());      // 输出: 5
System.out.println("当前容量: " + sb.capacity());    // 输出: 100

// 添加更多内容
sb.append(" World! This is a long string...");
System.out.println("当前长度: " + sb.length());      // 输出: 33
System.out.println("当前容量: " + sb.capacity());    // 输出: 202(自动扩容)

indexOf()和lastIndexOf()方法

StringBuffer sb = new StringBuffer("Java is fun, Java is powerful");

// 查找第一次出现的位置
int firstIndex = sb.indexOf("Java");
System.out.println("第一次出现位置: " + firstIndex); // 输出: 0

// 查找第二次出现的位置
int secondIndex = sb.indexOf("Java", firstIndex + 1);
System.out.println("第二次出现位置: " + secondIndex); // 输出: 15

// 查找最后一次出现的位置
int lastIndex = sb.lastIndexOf("Java");
System.out.println("最后一次出现位置: " + lastIndex); // 输出: 15

实战案例:日志解析器

public class LogParser {
    public static void parseLog(StringBuffer logBuffer) {
        int errorIndex = logBuffer.indexOf("ERROR");
        int warningIndex = logBuffer.indexOf("WARNING");
        
        if (errorIndex != -1) {
            System.out.println("发现错误日志,位置: " + errorIndex);
            // 提取错误信息
            int endOfLine = logBuffer.indexOf("\n", errorIndex);
            if (endOfLine != -1) {
                String errorLine = logBuffer.substring(errorIndex, endOfLine);
                System.out.println("错误详情: " + errorLine);
            }
        }
        
        if (warningIndex != -1) {
            System.out.println("发现警告日志,位置: " + warningIndex);
        }
    }
}

// 使用示例
StringBuffer log = new StringBuffer("2023-10-15 14:30:25 INFO: System started\n" +
                                   "2023-10-15 14:30:26 ERROR: Database connection failed\n" +
                                   "2023-10-15 14:30:27 WARNING: High memory usage");
LogParser.parseLog(log);

substring()方法

StringBuffer sb = new StringBuffer("Hello World");

// 获取子字符串
String sub1 = sb.substring(0, 5); // 左闭右开
System.out.println(sub1); // 输出: Hello

String sub2 = sb.substring(6);
System.out.println(sub2); // 输出: World

reverse()方法

StringBuffer sb = new StringBuffer("Hello");
sb.reverse();
System.out.println(sb.toString()); // 输出: olleH

// 实战:回文数检测
public static boolean isPalindrome(String str) {
    if (str == null) return false;
    
    StringBuffer sb = new StringBuffer(str);
    String reversed = sb.reverse().toString();
    return str.equals(reversed);
}

// 使用示例
System.out.println(isPalindrome("racecar")); // 输出: true
System.out.println(isPalindrome("hello"));   // 输出: false

4. 转换方法

toString()方法

StringBuffer sb = new StringBuffer("Hello");
String str = sb.toString(); // 转换为不可变的String
System.out.println(str.getClass()); // 输出: class java.lang.String

toString()的性能考虑

// 不推荐:频繁调用toString()会创建新的String对象
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 1000; i++) {
    sb.append(i).append(", ");
    String temp = sb.toString(); // 每次循环都创建新对象
}

// 推荐:只在最后调用一次toString()
StringBuffer sb2 = new StringBuffer();
for (int i = 0; i < 1000; i++) {
    sb2.append(i).append(", ");
}
String result = sb2.toString(); // 只在最后调用一次

三、StringBuffer的线程安全机制

1. 同步方法

StringBuffer的所有公开方法都使用synchronized关键字修饰,确保线程安全:

// StringBuffer的源码片段(简化)
public class StringBuffer extends AbstractStringBuilder {
    @Override
    public synchronized StringBuffer append(String str) {
        // 实现细节
        return this;
    }
    
    @Override
    public synchronized String toString() {
        // 实现细节
        return new String(value, 0, count);
    }
}

2. 多线程实战案例

public class MultiThreadStringBuffer {
    public static void main(String[] args) throws InterruptedException {
        final StringBuffer sharedBuffer = new StringBuffer();
        
        // 创建两个线程同时操作同一个StringBuffer
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                sharedBuffer.append("A");
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                sharedBuffer.append("B");
            }
        });
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        // 结果应该是200个字符,且不会出现数据混乱
        System.out.println("最终长度: " + sharedBuffer.length()); // 输出: 200
        System.out.println("内容: " + sharedBuffer.toString());
    }
}

3. 性能对比测试

public class StringBufferPerformanceTest {
    private static final int ITERATIONS = 100000;
    
    public static void testStringConcatenation() {
        long start = System.currentTimeMillis();
        String result = "";
        for (int i = 0; i < ITERATIONS; i++) {
            result += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("String拼接耗时: " + (end - start) + "ms");
    }
    
    public static void testStringBuffer() {
        long start = System.currentTimeMillis();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < ITERATIONS; i++) {
            sb.append(i);
        }
        String result = sb.toString();
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer耗时: " + (end - start) + "ms");
    }
    
    public static void testStringBuilder() {
        long start = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ITERATIONS; i++) {
            sb.append(i);
        }
        String result = sb.toString();
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder耗时: " + (end - start) + "ms");
    }
    
    public static void main(String[] args) {
        testStringConcatenation();
        testStringBuffer();
        testStringBuilder();
    }
}

典型输出

String拼接耗时: 1250ms
StringBuffer耗时: 15ms
StringBuilder耗时: 8ms

四、StringBuffer的实战应用技巧

1. 高效构建复杂字符串

技巧1:预分配容量

// 根据数据量预估容量
public String buildReport(List<Data> dataList) {
    // 假设每条数据平均50个字符,加上标题和分隔符
    int estimatedSize = dataList.size() * 50 + 100;
    StringBuffer report = new StringBuffer(estimatedSize);
    
    report.append("=== 数据报告 ===\n");
    report.append("数据量: ").append(dataList.size()).append("\n");
    report.append("生成时间: ").append(new Date()).append("\n\n");
    
    for (Data data : dataList) {
        report.append("ID: ").append(data.getId())
              .append(", Name: ").append(data.getName())
              .append(", Value: ").append(data.getValue())
              .append("\n");
    }
    
    return report.toString();
}

技巧2:链式调用

// 利用StringBuffer返回自身的特点,实现链式调用
StringBuffer sb = new StringBuffer()
    .append("用户: ").append(username).append("\n")
    .append("操作: ").append(action).append("\n")
    .append("时间: ").append(new Date()).append("\n")
    .append("结果: ").append(result).append("\n");

// 或者封装成工具方法
public static StringBuffer logEntry(StringBuffer sb, String user, String action, String result) {
    return sb.append("[").append(new Date()).append("] ")
             .append(user).append(" - ").append(action)
             .append(": ").append(result).append("\n");
}

// 使用
StringBuffer log = new StringBuffer();
logEntry(log, "admin", "login", "success");
logEntry(log, "user1", "upload", "failed");

2. 字符串模板处理

技巧:使用StringBuffer实现简单的模板引擎

public class SimpleTemplateEngine {
    private final StringBuffer template;
    
    public SimpleTemplateEngine(String template) {
        this.template = new StringBuffer(template);
    }
    
    public String process(Map<String, Object> data) {
        StringBuffer result = new StringBuffer(template);
        
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String placeholder = "${" + entry.getKey() + "}";
            int index = result.indexOf(placeholder);
            while (index != -1) {
                result.replace(index, index + placeholder.length(), 
                              String.valueOf(entry.getValue()));
                index = result.indexOf(placeholder, index);
            }
        }
        
        return result.toString();
    }
}

// 使用示例
SimpleTemplateEngine engine = new SimpleTemplateEngine(
    "尊敬的${name},您的订单${orderId}已${status},金额:${amount}元"
);

Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("orderId", "20231015001");
data.put("status", "发货");
data.put("amount", 99.99);

String message = engine.process(data);
System.out.println(message);
// 输出: 尊敬的张三,您的订单20231015001已发货,金额:99.99元

3. 文件内容处理

技巧:逐行读取并处理文件

public class FileProcessor {
    public static String processFile(String filePath) throws IOException {
        StringBuffer content = new StringBuffer();
        
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // 处理每行内容
                if (line.contains("ERROR")) {
                    content.append("[ERROR] ").append(line).append("\n");
                } else if (line.contains("WARNING")) {
                    content.append("[WARNING] ").append(line).append("\n");
                } else {
                    content.append("[INFO] ").append(line).append("\n");
                }
            }
        }
        
        return content.toString();
    }
}

4. 性能优化技巧

技巧1:避免不必要的toString()调用

// 不推荐
public String generateReport(List<Data> data) {
    StringBuffer sb = new StringBuffer();
    for (Data d : data) {
        sb.append(d.toString()); // 每次循环都调用toString()
    }
    return sb.toString();
}

// 推荐
public String generateReportOptimized(List<Data> data) {
    StringBuffer sb = new StringBuffer();
    for (Data d : data) {
        sb.append(d.getId()).append(":").append(d.getName());
    }
    return sb.toString();
}

技巧2:批量操作减少锁竞争

// 在多线程环境下,如果可能,批量操作StringBuffer
public class BatchProcessor {
    private final StringBuffer sharedBuffer = new StringBuffer();
    
    public void processBatch(List<String> items) {
        // 一次性添加多个项目,减少锁获取次数
        synchronized (sharedBuffer) {
            for (String item : items) {
                sharedBuffer.append(item).append("\n");
            }
        }
    }
}

五、StringBuffer的局限性及替代方案

1. 性能局限性

由于synchronized的开销,StringBuffer在单线程环境下性能不如StringBuilder。在Java 5之后,推荐:

  • 单线程环境:使用StringBuilder
  • 多线程环境:如果线程安全是必须的,使用StringBuffer;否则考虑使用StringBuilder + 外部同步

2. 内存占用

StringBuffer会预分配容量,可能导致内存浪费:

// 不推荐:预分配过大容量
StringBuffer sb = new StringBuffer(1000000); // 可能浪费内存

// 推荐:根据实际需求分配
StringBuffer sb = new StringBuffer(estimatedSize);

3. 现代替代方案

// Java 8+ Stream API(适合复杂处理)
String result = dataList.stream()
    .map(d -> d.getId() + ":" + d.getName())
    .collect(Collectors.joining("\n"));

// StringJoiner(Java 8+,专门用于连接字符串)
StringJoiner joiner = new StringJoiner(", ");
for (Data d : dataList) {
    joiner.add(d.getName());
}
String result = joiner.toString();

六、最佳实践总结

  1. 选择合适的类

    • 单线程:StringBuilder
    • 多线程:StringBuffer
    • 不可变:String
  2. 预分配容量:根据数据量预估,避免频繁扩容。

  3. 减少toString()调用:只在最终需要时调用一次。

  4. 链式调用:利用方法返回StringBuffer自身的特点,提高代码可读性。

  5. 线程安全考虑:如果多个线程共享StringBuffer,确保正确同步。

  6. 性能测试:在关键路径上进行性能测试,选择最适合的方案。

  7. 现代API结合:在Java 8+环境中,考虑结合StringJoinerStream API等现代特性。

七、常见问题解答

Q1:StringBuffer和StringBuilder的主要区别是什么? A:主要区别在于线程安全。StringBuffer是线程安全的(所有方法都synchronized),而StringBuilder不是。在单线程环境下,StringBuilder性能更好。

Q2:什么时候应该使用StringBuffer? A:当需要在多线程环境下共享可变字符串时,或者需要确保线程安全时。例如,多个线程同时向同一个日志缓冲区写入内容。

Q3:StringBuffer的扩容机制是怎样的? A:当StringBuffer的容量不足以容纳新字符时,它会自动扩容。扩容策略通常是:新容量 = 原容量 × 2 + 2。例如,初始容量为16,第一次扩容后为34(16×2+2),第二次为70(34×2+2)。

Q4:StringBuffer的reverse()方法有什么注意事项? A:reverse()方法会直接修改StringBuffer本身,不会创建新对象。如果需要保留原字符串,应该先复制:StringBuffer copy = new StringBuffer(original); copy.reverse();

Q5:在Java 9+中,StringBuffer还有使用价值吗? A:在Java 9+中,字符串的内部表示发生了变化(使用byte数组),但StringBufferStringBuilder仍然有价值,特别是在需要可变字符串或线程安全的场景。不过,对于简单的字符串拼接,Java编译器已经做了很多优化。

通过本文的详细讲解和实战案例,相信您已经对StringBuffer有了深入的理解。在实际开发中,请根据具体场景选择最合适的字符串处理方式,以达到最佳的性能和可维护性。