腾讯Java面试被问:String、StringBuffer、StringBuilder区别

一、核心区别对比表

维度 String StringBuffer StringBuilder
可变性 不可变 可变 可变
线程安全 ✅(天然安全) 线程安全(方法synchronized) 线程不安全
性能 最低(频繁修改时) 中等 最高
底层存储 final char[] char[](可变) char[](可变)
内存开销 最高(产生大量中间对象) 较低 最低
JVM优化 字符串常量池 无特殊优化 无特殊优化
继承关系 实现CharSequence 继承AbstractStringBuilder 继承AbstractStringBuilder

二、底层源码深度解析

1. String的不可变性(核心设计)

java

复制

下载

复制代码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    // ✅ 关键:final修饰的char数组
    private final char value[];
    
    // 每次"修改"都创建新对象
    public String concat(String str) {
        // ... 创建新char数组,复制数据
        return new String(buf, true);
    }
}

不可变性的好处

  • 线程安全:天然线程安全

  • 安全性:适合作为Map键值、网络连接参数等

  • 缓存哈希码:只需计算一次,提高集合性能

  • 字符串常量池:实现字符串复用,节省内存

2. StringBuilder和StringBuffer的共性

java

复制

下载

复制代码
// 共同的父类
abstract class AbstractStringBuilder {
    // ✅ 关键:非final的char数组
    char[] value;
    
    // 容量可动态扩容
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        value = Arrays.copyOf(value, newCapacity);
    }
}

3. StringBuilder vs StringBuffer的关键差异

java

复制

下载

复制代码
// StringBuilder(JDK 1.5+)
public final class StringBuilder extends AbstractStringBuilder {
    // 方法没有同步
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
}

// StringBuffer(JDK 1.0)
public final class StringBuffer extends AbstractStringBuilder {
    // ✅ 关键:所有方法都有synchronized
    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }
    
    public synchronized String toString() {
        // 第一次调用时缓存结果
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】


三、性能对比实测

测试代码示例

java

复制

下载

复制代码
public class StringPerformanceTest {
    private static final int LOOP_COUNT = 100000;
    
    public static void main(String[] args) {
        // 测试1:String拼接(最差)
        long start1 = System.currentTimeMillis();
        String str1 = "";
        for (int i = 0; i < LOOP_COUNT; i++) {
            str1 += "a";  // 每次循环创建新String对象
        }
        System.out.println("String耗时: " + (System.currentTimeMillis() - start1) + "ms");
        
        // 测试2:StringBuilder(最优)
        long start2 = System.currentTimeMillis();
        StringBuilder sb2 = new StringBuilder();
        for (int i = 0; i < LOOP_COUNT; i++) {
            sb2.append("a");  // 原地修改
        }
        String result2 = sb2.toString();
        System.out.println("StringBuilder耗时: " + (System.currentTimeMillis() - start2) + "ms");
        
        // 测试3:StringBuffer(中等)
        long start3 = System.currentTimeMillis();
        StringBuffer sb3 = new StringBuffer();
        for (int i = 0; i < LOOP_COUNT; i++) {
            sb3.append("a");  // synchronized有开销
        }
        String result3 = sb3.toString();
        System.out.println("StringBuffer耗时: " + (System.currentTimeMillis() - start3) + "ms");
    }
}

典型测试结果(10万次拼接)

text

复制

下载

复制代码
String耗时: 3850ms        # 最慢,产生大量中间对象
StringBuilder耗时: 5ms    # 最快,无锁开销
StringBuffer耗时: 15ms    # 较慢,有同步开销

内存占用对比

java

复制

下载

复制代码
// String拼接:产生约10万个String对象 + 10万个char[]
// StringBuilder:只产生1个StringBuilder对象 + 少量扩容的char[]
// StringBuffer:同StringBuilder,但有锁对象开销

四、使用场景指南

优先使用String的场景

  1. 字符串常量或不需修改的情况

    java

    复制

    下载

    复制代码
    String url = "https://api.example.com";
    String fileName = "config.properties";
  2. 作为HashMap的键

    java

    复制

    下载

    复制代码
    Map<String, User> userMap = new HashMap<>();
    userMap.put("user001", new User()); // String不可变,保证键值稳定
  3. 安全敏感信息存储

    java

    复制

    下载

    复制代码
    String password = getPassword();
    // 使用后可以快速置null,StringBuffer/StringBuilder需要遍历清空数组
  4. 多线程共享且不修改的字符串

    java

    复制

    下载

    复制代码
    public static final String DEFAULT_ENCODING = "UTF-8";

优先使用StringBuilder的场景

  1. 单线程下的字符串拼接

    java

    复制

    下载

    复制代码
    public String buildQuery(Map<String, String> params) {
        StringBuilder query = new StringBuilder();
        query.append("SELECT * FROM users WHERE ");
        
        for (Map.Entry<String, String> entry : params.entrySet()) {
            query.append(entry.getKey())
                 .append("='")
                 .append(entry.getValue())
                 .append("' AND ");
        }
        
        // 删除最后的" AND "
        query.delete(query.length() - 5, query.length());
        return query.toString();
    }
  2. 循环体内的字符串操作

    java

    复制

    下载

    复制代码
    // 好的做法
    StringBuilder result = new StringBuilder();
    for (User user : userList) {
        result.append(user.getName()).append(",");
    }
    
    // 差的做法(性能差)
    String result = "";
    for (User user : userList) {
        result += user.getName() + ",";
    }
  3. SQL/JSON/XML动态构建

    java

    复制

    下载

    复制代码
    public String toJson(List<User> users) {
        StringBuilder json = new StringBuilder("[");
        for (int i = 0; i < users.size(); i++) {
            json.append(users.get(i).toJsonString());
            if (i < users.size() - 1) json.append(",");
        }
        json.append("]");
        return json.toString();
    }

优先使用StringBuffer的场景

  1. 多线程环境下的字符串操作

    java

    复制

    下载

    复制代码
    public class ThreadSafeLogger {
        private StringBuffer logBuffer = new StringBuffer();
        
        // 多个线程可能同时调用此方法
        public synchronized void log(String message) {
            logBuffer.append(Thread.currentThread().getName())
                     .append(": ")
                     .append(message)
                     .append("\n");
        }
        
        public String getLog() {
            return logBuffer.toString();
        }
    }
  2. 全局共享的字符串缓冲区

    java

    复制

    下载

    复制代码
    public class SharedStringHolder {
        // 多个线程需要修改同一个字符串
        private static StringBuffer sharedBuffer = new StringBuffer();
        
        public static void appendSafe(String text) {
            sharedBuffer.append(text); // 线程安全
        }
    }

五、高级技巧与最佳实践

1. 初始化容量优化

java

复制

下载

复制代码
// 不好的做法:默认容量16,频繁扩容
StringBuilder sb1 = new StringBuilder();
sb1.append("很长的字符串..."); // 可能触发扩容

// 好的做法:预估容量,避免扩容开销
int estimatedLength = 1000;
StringBuilder sb2 = new StringBuilder(estimatedLength);

2. 链式调用优势

java

复制

下载

复制代码
// StringBuilder/StringBuffer支持链式调用
String result = new StringBuilder()
    .append("SELECT ")
    .append(fields)
    .append(" FROM ")
    .append(tableName)
    .append(" WHERE id=")
    .append(id)
    .toString();

3. JDK 9+的性能优化

java

复制

下载

复制代码
// JDK 9后,String内部使用byte[]存储(Latin1字符用1字节)
// 但对外API保持不变,仍然不可变

// JDK 9的字符串拼接优化
String a = "Hello";
String b = "World";
String c = a + b; // 编译器可能优化为invokedynamic

4. 性能敏感场景的特殊处理

java

复制

下载

复制代码
// 超高性能场景:直接操作char[]
public char[] toCharArrayDirect() {
    char[] result = new char[length];
    System.arraycopy(value, 0, result, 0, length);
    return result;
}

// 使用ThreadLocal避免StringBuilder重复创建
private static final ThreadLocal<StringBuilder> threadLocalStringBuilder =
    ThreadLocal.withInitial(() -> new StringBuilder(512));

六、面试深度回答要点

基础回答(校招/初级)

"String是不可变的,每次修改都创建新对象;StringBuffer和StringBuilder是可变的。StringBuffer是线程安全的但性能较低,StringBuilder线程不安全但性能最高。单线程用StringBuilder,多线程用StringBuffer。"

进阶回答(社招/中级)

"从源码看,String的不可变性由final char[]保证,这带来了线程安全、缓存哈希码、字符串常量池等好处。StringBuffer通过synchronized实现线程安全,有锁开销;StringBuilder无锁。在性能敏感场景要预估容量,避免扩容开销。JDK 9后String内部改用byte[]存储以节省内存。"

深度回答(高级/架构师)

"这本质上是不可变对象可变对象的设计哲学差异。String的不可变性牺牲了修改性能,但换来了安全性和可预测性,适合作为系统边界的数据载体。StringBuilder采用可变设计+无锁,实现了极致性能。在实际架构中,我们通常用String作为DTO、配置项,用StringBuilder处理模板渲染、SQL构建,用StringBuffer处理多线程日志拼接。在微服务间传递数据时,String的不可变性还能防止意外的数据修改。"

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

高频扩展问题

  1. String str = "a" + "b" + "c"创建了几个对象?

    • 编译期优化后只创建1个:"abc"(常量折叠)
  2. new String("abc")创建了几个对象?

    • 可能1个或2个:常量池中的"abc"(如有)+ 堆中的新String对象
  3. StringBuilder的默认容量和扩容规则?

    • 默认16字符,扩容:newCapacity = oldCapacity * 2 + 2
  4. 为什么StringBuffer的toString()有缓存?

    • 防止多次调用toString()重复创建char[],但toStringCache只在toString()中有效
  5. String的intern()方法作用?

    • 将字符串放入常量池,相同内容的字符串返回同一个引用

掌握这三者的区别,不仅是为了面试,更是在实际开发中写出高性能、可维护代码的基础。根据场景选择合适的工具,是每个Java工程师的基本功。

相关推荐
长安第一美人4 小时前
php出现zend_mm_heap corrupted 或者Segment fault
开发语言·嵌入式硬件·php·zmq·工业应用开发
源码获取_wx:Fegn08954 小时前
基于springboot + vue心理健康管理系统
vue.js·spring boot·后端
优弧4 小时前
离开舒适区100天,我后悔了吗?
前端·后端·面试
gihigo19984 小时前
基于MATLAB的电力系统经济调度实现
开发语言·matlab
麻辣兔变形记5 小时前
深入理解微服务下的 Saga 模式——以电商下单为例
微服务·云原生·架构
飛6795 小时前
从 0 到 1 掌握 Flutter 状态管理:Provider 实战与原理剖析
开发语言·javascript·ecmascript
龚礼鹏5 小时前
Android应用程序 c/c++ 崩溃排查流程
c语言·开发语言·c++
Filotimo_5 小时前
在java开发中,什么是JSON格式
开发语言·json
Vic101015 小时前
解决 Spring Security 在异步线程中用户信息丢失的问题
java·前端·spring