面试篇-String、StringBuffer和StringBuilder有什么区别?
* 一、基本概念 * 1. String字符串 * - Java中最基础的字符串类位于java.lang包 * - 一旦创建内容就不能改变不可变 * - 每次修改字符串实际上是创建了一个新的String对象 * - 原对象依然存在只是引用指向了新对象 * - 内存开销频繁修改会产生大量垃圾对象 * * 2. StringBuffer字符串缓冲区 * - 可变的字符串类位于java.lang包 * - 线程安全synchronized修饰适合多线程环境 * - 效率比String高但比StringBuilder低因为线程安全有开销 * * 3. StringBuilder字符串构建器 * - 可变的字符串类位于java.lang包 * - 线程不安全但效率最高 * - JDK 5.0新增推荐在单线程环境下使用* 二、核心区别对比 * ┌─────────────┬──────────────┬──────────────┬──────────────┐ * │ 对比项 │ String │ StringBuffer │ StringBuilder│ * ├─────────────┼──────────────┼──────────────┼──────────────┤ * │ 可变性 │ 不可变 │ 可变 │ 可变 │ * │ 线程安全 │ 安全不可变│ 安全 │ 不安全 │ * │ 效率 │ 低频繁创建│ 中 │ 高 │ * │ 适用场景 │ 少量字符串 │ 多线程 │ 单线程 │ * │ 底层实现 │ char[] │ char[] │ char[] │ * │ introduced │ JDK 1.0 │ JDK 1.0 │ JDK 5.0 │ * └─────────────┴──────────────┴──────────────┴──────────────┘* 三、为什么String不可变 * 1. 字符串常量池优化相同内容的字符串只存一份节省内存 * 2. 安全性不可变对象天生线程安全适合做Map的key * 3. 缓存优化hashCode可以缓存提高哈希表性能* 四、使用建议 * 1. 字符串少量操作 → 用 String * 2. 多线程环境下字符串频繁修改 → 用 StringBuffer * 3. 单线程环境下字符串频繁修改 → 用 StringBuilder推荐第一部分演示String的不可变性public class StringDemo { public static void main(String[] args) { System.out.println( String vs StringBuffer vs StringBuilder); // 第一部分演示String的不可变性 demoStringImmutable(); } /** * String的不可变性【知识点】 * 1. String对象一旦创建内容就不能改变 * 2. 看似修改字符串实际是创建了新对象 * 3. 原对象依然存在只是引用指向了新对象 * 4. 内存开销频繁修改会产生大量垃圾对象 */ private static void demoStringImmutable() { System.out.println(【第一部分】String的不可变性演示\n); // 第1步创建一个String对象 String str Hello; // 在字符串常量池中创建Hello System.out.println(1. 初始字符串 str); // Hello System.out.println( 内存地址 getAddress(str)); // 0xf6f4d33 // 第2步执行拼接操作 str str World; // 实际创建了新对象Hello World System.out.println(2. 拼接后字符串 str); // Hello World System.out.println( 内存地址 getAddress(str)); // 0x3f99bd52 // 第3步再执行一次拼接 str str !; // 又创建了新对象Hello World! System.out.println(3. 再次拼接后 str); // Hello World! System.out.println( 内存地址 getAddress(str)); // 0x4f023edb } }说明- 每次拼接都创建了新对象原对象变成垃圾等待回收- 内存地址每次都变化证明创建了新对象- 这就是String效率低的原因第二部分演示StringBuffer的可变性public class StringDemo { public static void main(String[] args) { System.out.println( String vs StringBuffer vs StringBuilder); // 第二部分演示StringBuffer的可变性 demoStringBuffer(); } /** * 演示StringBuffer的可变性【知识点】 * 1. StringBuffer内部维护一个可变的字符数组 * 2. 修改内容时直接在原对象上修改不创建新对象 * 3. 线程安全方法用synchronized修饰同一时间只能一个线程访问 * 4. 默认容量16个字符不够时自动扩容原容量*22 */ private static void demoStringBuffer() { System.out.println(【第二部分】StringBuffer的可变性演示\n); // 第1步创建StringBuffer对象 // 知识点推荐指定初始容量避免频繁扩容 StringBuffer sb new StringBuffer(Hello); System.out.println(1. 初始内容 sb); // Hello System.out.println( 内存地址 getAddress(sb)); // 0x3a71f4dd System.out.println( 容量 sb.capacity()); // 默认16521初始值字符串长度// 21 // 第2步追加内容在原对象上修改 sb.append( World); // 在原对象上追加不创建新对象 System.out.println(2. 追加后 sb); // Hello World System.out.println( 内存地址 getAddress(sb)); // 0x3a71f4dd // 第3步插入内容 sb.insert(5, Beautiful); // 在索引5的位置插入 System.out.println(3. 插入后 sb); // Hello Beautiful World // 第4步删除内容 sb.delete(5, 15); // 删除索引5到14的内容左闭右开 System.out.println(4. 删除后 sb); // Hello World // 第5步替换内容 sb.replace(0, 5, Hi); // 将索引0-4的内容替换为Hi System.out.println(5. 替换后 sb); // Hi World // 第6步反转字符串 sb.reverse(); // 反转整个字符串 System.out.println(6. 反转后 sb); // dlroW iH }说明- 内存地址始终不变证明一直在操作同一个对象- 提供了丰富的操作方法append / insert / delete / replace / reverse- 线程安全但效率略低于StringBuilder第三部分演示StringBuilder的可变性public class StringDemo { public static void main(String[] args) { System.out.println( String vs StringBuffer vs StringBuilder); // 第三部分演示StringBuilder的可变性 demoStringBuilder(); } /** * 演示StringBuilder的可变性【知识点】 * 1. StringBuilder和StringBuffer用法几乎一样 * 2. 主要区别StringBuilder没有synchronized线程不安全 * 3. 单线程环境下效率比StringBuffer高10%-15% * 4. JDK 5.0新增推荐日常使用 */ private static void demoStringBuilder() { System.out.println(【第三部分】StringBuilder的可变性演示\n); // 创建StringBuilder对象 StringBuilder sbd new StringBuilder(Hello); System.out.println(1. 初始内容 sbd); // Hello System.out.println( 内存地址 getAddress(sbd)); // 0x85ede7b // 追加内容 sbd.append( World); System.out.println(2. 追加后 sbd); // Hello World System.out.println( 内存地址 getAddress(sbd)); // 0x85ede7b // 插入内容 sbd.insert(6, Java); System.out.println(3. 插入后 sbd);// Hello JavaWorld // 删除内容 sbd.delete(6, 11); System.out.println(4. 删除后 sbd);// Hello World } }说明- 用法和StringBuffer完全一样- 单线程环境下优先使用StringBuilder- 内存地址不变证明操作的是同一个对象第四部分性能对比public class StringDemo { public static void main(String[] args) { System.out.println( String vs StringBuffer vs StringBuilder); // 第四部分性能对比 performanceComparison(); } /** * 性能对比演示【知识点】 * 1. 循环拼接字符串时性能差异非常明显 * 2. String每次拼接创建新对象O(n²)时间复杂度 * 3. StringBuffer/StringBuilder原地修改O(n)时间复杂度 * 4. 10000次循环String可能耗时几秒StringBuilder只需几毫秒 */ private static void performanceComparison() { System.out.println(【第四部分】性能对比演示循环10000次拼接\n); int count 10000; // 测试String long start1 System.currentTimeMillis(); // 记录开始时间毫秒 String str ; for (int i 0; i count; i) { str i; // 每次循环都创建新对象 } long end1 System.currentTimeMillis(); // 记录结束时间 System.out.println(String 耗时 (end1 - start1) ms);// String 耗时182ms // 测试StringBuffer long start2 System.currentTimeMillis(); StringBuffer sb new StringBuffer(); for (int i 0; i count; i) { sb.append(i); // 原地修改不创建新对象 } long end2 System.currentTimeMillis(); System.out.println(StringBuffer 耗时 (end2 - start2) ms);// StringBuffer 耗时2ms // 测试StringBuilder long start3 System.currentTimeMillis(); StringBuilder sbd new StringBuilder(); for (int i 0; i count; i) { sbd.append(i); // 原地修改不创建新对象 } long end3 System.currentTimeMillis(); System.out.println(StringBuilder 耗时 (end3 - start3) ms);// StringBuilder 耗时8ms } }说明- String最慢因为每次拼接都创建新对象- StringBuffer和StringBuilder快得多- StringBuilder略快于StringBuffer无同步开销- 循环次数越多差距越明显【第五部分】线程安全说明1. String - 线程安全- 不可变对象多个线程共享不会出问题- 就像共享一本书谁都不能改所以安全2. StringBuffer - 线程安全- 方法加了synchronized同步锁- 同一时间只能一个线程操作像公共厕所只有一个坑位- 安全但效率低因为要排队等待3. StringBuilder - 线程不安全- 没有同步机制多个线程同时修改会混乱- 就像多人同时写一块黑板字会重叠混乱- 但单线程下效率最高推荐日常使用【使用建议】- 单线程、字符串频繁修改 → 用 StringBuilder- 多线程、字符串频繁修改 → 用 StringBuffer- 字符串不经常修改、作为Map的key → 用 String* 【实际应用场景】 * 场景1字符串常量、配置信息、Map的key → 用String * 场景2循环拼接字符串、SQL拼接、JSON构建 → 用StringBuilder * 场景3多线程环境下的字符串操作 → 用StringBuffer