Java字符串三兄弟:String、StringBuffer和StringBuilder的核心区别

String三兄弟核心区别解析


概要

在Java编程中,字符串是我们最常打交道的数据类型之一。然而,面对String、StringBuffer和StringBuilder这三个看似相似的类,很多开发者都会感到困惑:它们到底有什么区别?在何种场景下该使用谁?本文将带你拨开迷雾,深入剖析这“字符串三兄弟”的底层原理与核心差异,让你在日后开发中能够做出最明智、最高效的选择。


1. String(字符串)—— 写在石头上的字

通俗理解:
String 就像你用凿子在石头上刻字。一旦刻上去,这个字就永远不能修改了。如果你想在“Hello”后面加一个“World”,你不是在原石头上修改,而是必须找一块全新的石头,重新刻上“Hello World”。原来的那块写着“Hello”的石头还在,只是你不再用它了。

专业解释:

  • 不可变性: String 对象一旦被创建,其值就不能被改变。任何对 String 的修改(如拼接、替换)操作,实际上都是创建了一个全新的 String 对象

  • 字符串常量池: 为了提高效率,Java 有一个特殊的内存区域叫“字符串常量池”。当你写 String s1 = "Hello",Java 会先去池里找有没有现成的“Hello”,如果有就直接用,没有才创建一个。这可以节省内存。

举例说明:

String str = "Hello";
str = str + " World"; // 看起来是修改了str,其实是创建了一个新对象"Hello World"
System.out.println(str); // 输出:Hello World

// 我们来验证一下它是否是新对象
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
String str4 = str1 + " World";

System.out.println(str1 == str2); // true,因为它们指向常量池里的同一个对象
System.out.println(str1 == str3); // false,str3是强制在堆里新创建的对象
System.out.println(str1.equals(str3)); // true,equals比较的是内容,所以为真
public class StringExample {
    public static void main(String[] args) {
        String result = "";
        
        // 在循环中拼接字符串 - 非常低效!
        for (int i = 0; i < 5; i++) {
            result += "数字:" + i + " "; // 每次循环都创建新对象
            System.out.println("第" + i + "次循环,result的内存地址:" + System.identityHashCode(result));
        }
        
        System.out.println("最终结果:" + result);
    }
}


输出效果:
第0次循环,result的内存地址:1324119927
第1次循环,result的内存地址:1878246837
第2次循环,result的内存地址:1430317222
第3次循环,result的内存地址:2065951873
第4次循环,result的内存地址:1791741888
最终结果:数字:0 数字:1 数字:2 数字:3 数字:4 

注意: 每次循环result的内存地址都不同,说明每次都创建了新对象!

特点总结:

  • 优点: 安全(因为不可变)、简单。

  • 缺点: 频繁修改时(比如在循环中拼接),会产生大量垃圾对象,效率非常低。


2. StringBuffer(字符串缓冲区)—— 写在黑板上的字(带锁)

通俗理解:
StringBuffer 就像一块黑板。你可以在上面随意写字、擦掉、添加内容。最关键的是,这块黑板带有一把锁。当一个人(一个线程)在上面写字时,他会把门锁上,其他人只能在外面等着,等他写完了,下一个人才可以进来写。这保证了不会出现两个人同时修改,把字写花的情况。

专业解释:

  • 可变性: StringBuffer 是一个可变的字符序列。你可以调用它的方法(如 .append().insert().delete())来修改内容,而不会创建新对象

  • 线程安全: 它的所有方法都是 synchronized(同步的),意味着它是多线程安全的。多个线程同时操作时,数据不会错乱。

举例说明:

// 创建一个StringBuffer,初始内容是"Hello"
StringBuffer sbuffer = new StringBuffer("Hello");

// 进行各种修改,都是在原对象上操作
sbuffer.append(" World"); // 拼接:Hello World
sbuffer.insert(5, ",");   // 在索引5的位置插入逗号:Hello, World
sbuffer.delete(5, 6);     // 删除索引5到6之间的字符(删掉了逗号):Hello World
sbuffer.reverse();        // 反转:dlroW olleH

System.out.println(sbuffer.toString()); // 输出:dlroW olleH

特点总结:

  • 优点: 可变的,频繁修改时效率高;线程安全。

  • 缺点: 因为加了锁,在单线程环境下,性能上有一点点损耗。


3. StringBuilder(字符串建造者)—— 写在笔记本上的字

通俗理解:
StringBuilder 和 StringBuffer 几乎一模一样,也是一块可以随意涂改的“黑板”(可变字符序列)。唯一的区别是,这块黑板没有锁。它就像你自己的私人笔记本,只有你一个人在用,所以不需要上锁,想怎么写就怎么写,速度更快。

专业解释:

  • 可变性: 和 StringBuffer 一样,是可变的。

  • 非线程安全: 它的方法不是 synchronized 的。因此,它在单线程环境下性能比 StringBuffer 更高。但如果多个线程同时操作它,可能会导致数据不一致。

举例说明:
它的用法和 StringBuffer 完全一样,只是名字不同。

// StringBuilder的用法和StringBuffer一模一样
StringBuilder sbuilder = new StringBuilder("Hello");

sbuilder.append(" World");
sbuilder.insert(5, ",");
sbuilder.delete(5, 6);
sbuilder.reverse();

System.out.println(sbuilder.toString()); // 输出:dlroW olleH

public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        
        for (int i = 0; i < 5; i++) {
            sb.append("数字:").append(i).append(" "); // 在原对象上修改
            System.out.println("第" + i + "次循环,sb的内存地址:" + System.identityHashCode(sb));
        }
        
        System.out.println("最终结果:" + sb.toString());
    }
}


输出效果:
第0次循环,sb的内存地址:1324119927
第1次循环,sb的内存地址:1324119927
第2次循环,sb的内存地址:1324119927
第3次循环,sb的内存地址:1324119927
第4次循环,sb的内存地址:1324119927
最终结果:数字:0 数字:1 数字:2 数字:3 数字:4 

注意: 内存地址始终不变,说明始终是同一个对象!

特点总结:

  • 优点: 可变的,在单线程下,它是三者中性能最高的。

  • 缺点: 线程不安全。


性能测试

public class PerformanceTest {
    public static void main(String[] args) {
        int count = 10000; // 测试10000次拼接
        
        // String 测试
        long startTime1 = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < count; i++) {
            str += "test";
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("String 耗时:" + (endTime1 - startTime1) + " 毫秒");
        
        // StringBuilder 测试
        long startTime2 = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append("test");
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("StringBuilder 耗时:" + (endTime2 - startTime2) + " 毫秒");
        
        // StringBuffer 测试
        long startTime3 = System.currentTimeMillis();
        StringBuffer sbf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            sbf.append("test");
        }
        long endTime3 = System.currentTimeMillis();
        System.out.println("StringBuffer 耗时:" + (endTime3 - startTime3) + " 毫秒");
    }
}



输出结果:可以看到性能差距非常大!
String 耗时:208 毫秒
StringBuilder 耗时:1 毫秒
StringBuffer 耗时:2 毫秒

小结

特性StringStringBufferStringBuilder
可变性不可变可变可变
线程安全安全(因为不可变)安全(同步锁)不安全
性能修改时最低较高(因有锁,稍慢)最高
使用场景操作少量数据,或不需要改变字符串内容多线程环境下,操作大量可变字符串单线程环境下,操作大量可变字符串
场景选择理由
操作少,字符串不变String简单安全,利用常量池
单线程,频繁操作StringBuilder性能最优,无锁开销
多线程,频繁操作StringBuffer线程安全,防止并发问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值