【Java基础】String不可变性、String vs StringBuffer vs StringBuilder、常量池、intern()方法(附《思维导图》+《面试考点背诵版》)

简介: 本文系统解析Java字符串核心机制:String不可变性(`final char[]`实现)、String/StringBuffer/StringBuilder三者对比(可变性/线程安全/性能)、字符串常量池(JDK7+移至堆)及`intern()`方法(JDK7+直接存堆引用)。

思维导图

Java 基础:String不可变性、String vs StringBuffer vs StringBuilder、常量池、intern()方法

一、String 不可变性(核心本质)

1. 定义

String 一旦创建,内容无法修改,任何修改字符串的操作都会生成新字符串,原字符串不变。

2. 底层源码原因

  1. String 底层使用 private final char[] value 存储字符
  2. final 修饰数组引用:数组地址不可改
  3. private 修饰:外部无法直接修改数组元素
  4. 无对外提供修改数组的方法,彻底锁死内容

3. 常见误区

  • s += "xxx" 不是修改原字符串,是新建对象
  • replace()、substring()、trim() 全都返回新 String
  • 不可变 ≠ 引用不可变:String s="a";s="b" 只是引用指向改变

4. 不可变性四大优势

  1. 线程安全:只读不可改,多线程并发无冲突
  2. 常量池复用:节省内存,相同字符串只存一份
  3. 哈希值固定:可安全作为 HashMap 键
  4. 数据安全:参数传递、网络传输不会被篡改

二、String / StringBuffer / StringBuilder 三者终极对比

特性 String StringBuffer StringBuilder
可变性 不可变 可变 可变
线程安全 安全 安全(synchronized) 不安全
效率 极低(频繁拼接大量创建新对象) 中等 最高
底层 final char[] 可变 char[] 扩容 可变 char[] 扩容
适用场景 少量字符串定义、常量 多线程拼接 单线程大量拼接
继承关系 独立类 继承 AbstractStringBuilder 继承 AbstractStringBuilder

核心执行逻辑

  1. String:拼接=新建对象,海量拼接极度耗内存
  2. StringBuffer:方法加锁,并发安全,加锁损耗性能
  3. StringBuilder:无锁,单线程最快,并发会数据错乱

开发使用规范

  • 日常字符串赋值、常量:用 String
  • 单线程循环拼接、日志、SQL拼接:优先 StringBuilder
  • 多线程共享字符串拼接:只能用 StringBuffer

三、字符串常量池(String Pool)

1. 常量池位置

  • JDK1.6 及之前:方法区永久代
  • JDK1.7+:移至堆内存(重点考点)

2. 常量池作用

缓存字面量字符串,实现字符串复用,减少重复对象创建,节省堆内存。

3. 两种创建字符串方式(重中之重)

方式1:字面量创建 String s1 = "abc"

  1. 先去字符串常量池查找
  2. 存在:直接返回池中引用
  3. 不存在:在常量池创建对象,返回引用
  4. 只创建 1 个对象

方式2:new 创建 String s2 = new String("abc")

  1. 先查常量池有无 "abc"
  2. 无:常量池创建1个
  3. 堆内存强制再新建1个 String 对象
  4. 最少创建 2 个对象

4. 相等判断规则

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
  • s1 == s2true 同一常量池引用
  • s1 == s3false 池内对象 != 堆new对象
  • s1.equals(s3)true 比较内容

5. 编译期优化

常量拼接直接在编译期合并放入常量池

String a = "a"+"b"; // 编译直接变成 "ab" 进常量池

变量拼接编译期无法优化,走堆对象

String x = "a";
String b = x + "b"; // 堆中新对象,不入常量池

四、intern() 方法 全网最全解析

1. 方法作用

public native String intern()
主动将字符串放入字符串常量池,并返回池内引用

2. JDK1.6 与 JDK1.7 核心区别(面试必问)

JDK1.6(永久代常量池)

  1. 常量池有该字符串:返回池引用
  2. 常量池没有:复制一份放入永久代常量池,返回新池引用
  3. 堆对象和池对象完全独立

JDK1.7+(堆内常量池)

  1. 常量池有:直接返回池引用
  2. 常量池没有不新建对象,直接把堆中对象引用存入常量池
  3. 实现堆对象 == 池对象,内存极致优化

3. 经典面试代码案例(JDK1.8)

String s = new String("1") + new String("1");
s.intern();
String s2 = "11";
System.out.println(s == s2); // true

解析

  1. s 是堆中 11 对象,常量池无
  2. s.intern():把堆s的引用存入常量池
  3. s2="11" 直接复用池内堆引用
  4. 两者指向同一堆对象 → true

4. intern() 使用场景

  1. 海量重复字符串,手动入池节省内存
  2. 统一字符串引用,== 判断生效
  3. 大数据、日志批量处理优化内存

五、高频面试考点汇总

  1. String 为什么不可变?final 数组+私有无修改方法
  2. 为什么推荐字符串拼接用 StringBuilder?效率最高无锁
  3. new String() 创建几个对象?1~2个
  4. 常量池位置变化:1.7从永久代移到堆
  5. 变量拼接和常量拼接区别:编译期优化
  6. intern() 1.6和1.7最大差异:是否复用堆引用
  7. String 不可变好处:安全、池化、哈希稳定

六、思维导图精简骨架

String体系
├─ 不可变性
│  ├─ 底层final char[]
│  ├─ 修改生成新对象
│  └─ 四大应用优势
├─ 三类字符串对比
│  ├─ String:不可变、低效
│  ├─ StringBuffer:线程安全、中等
│  └─ StringBuilder:单线程最快
├─ 字符串常量池
│  ├─ 内存位置变迁
│  ├─ 字面量 / new 两种创建
│  └─ 编译期拼接优化
└─ intern()方法
   ├─ 作用:入池
   ├─ JDK6 / JDK7 差异
   └─ 实战内存优化

面试背诵短句版

一、面试极速背诵版(精简口诀+满分答案)

1. String不可变性

  1. 底层private final char[] value 存储字符
  2. 不可变原因:final锁数组地址、private禁止外部修改、无修改API
  3. 修改本质:所有增删改查方法都新建字符串,原对象不变
  4. 四大好处:线程安全、常量池复用、hash值稳定、数据安全
  5. 误区:引用可变,内容不可变

2. String / StringBuilder / StringBuffer

  1. String:不可变,拼接低效,适合少量固定字符串
  2. StringBuffer:可变,加synchronized锁,线程安全,效率偏低
  3. StringBuilder:可变,无锁,单线程效率最高,线程不安全
  4. 选型口诀:常量用String、单拼用Builder、并发拼接用Buffer

3. 字符串常量池

  1. 位置:JDK1.6永久代,JDK1.7+移至堆内存
  2. 字面量创建String s="abc" → 优先走常量池,最多1个对象
  3. new创建new String("abc") → 常量池+堆,最少2个对象
  4. 编译优化常量直接拼接编译期合并入池;变量拼接生成堆新对象

4. intern() 方法

  1. 作用:将字符串主动放入常量池,返回池中引用
  2. JDK1.6:无则复制新对象进永久代,堆与池不是同一对象
  3. JDK1.7+:无则直接存堆对象引用,堆和池指向同一个
  4. 用途:海量字符串去重、节省内存、统一引用判断

二、10道高频面试真题(含标准答案)

题1:String为什么不可变?

答:底层由private final char[]存储,final保证数组引用不可修改,private禁止外部访问修改,类中没有提供修改字符数组的方法,所以内容一旦创建无法更改。

题2:String拼接"+"底层原理是什么?

答:JDK8底层会自动创建StringBuilder进行append拼接,最后toString转String;循环内频繁+会频繁创建销毁Builder,性能极差。

题3:三者效率排序?

答:StringBuilder > StringBuffer > String

题4:String s = new String("xyz"); 创建几个对象?

答:常量池无xyz:2个(常量池1个+堆1个);常量池已有:1个(仅堆对象)。

题5:== 和 equals 区别?

答:==比较内存地址,equals默认比较地址,String重写后比较内容

题6:字符串常量池存在意义?

答:复用字符串对象,减少内存占用,避免大量重复字符串创建,提升性能。

题7:String s1="a"+"b"String s2=x+"b" 区别?

答:s1编译期优化为"ab",直接入常量池;s2是变量拼接,运行时创建堆中新对象,不入常量池。

题8:intern()方法JDK7之后变化?

答:池中没有该字符串时,不再创建新对象,直接把堆内对象引用存入常量池,实现堆对象与池对象同一地址。

题9:为什么String适合做HashMap的key?

答:不可变,hashCode值固定,不会变动导致找不到键,线程安全。

题10:循环拼接字符串用哪个?为什么?

答:用StringBuilder,可变字符数组,只扩容不新建对象,无线程锁,执行效率最高。

三、必背代码结论(JDK8环境)

// 1
String s1 = "abc";
String s2 = "abc";
s1 == s2 → true

// 2
String s3 = new String("abc");
s1 == s3 → false

// 3
String s = new String("1")+new String("1");
s.intern();
String s4 = "11";
s == s4 → true

Java String 全套【易混淆易错点对照表】

一、不可变性 易错区分

错误认知 正确结论
String 引用不能变 引用可以变,内容不能变
s+=拼接修改原字符串 每次拼接新建新对象,原对象不变
substring 截取修改原串 全部返回新字符串
final String 就不可改 final只限制引用,内容依旧遵循不可变

一句话记:地址可改,字符内容永不可改。


二、String / StringBuilder / StringBuffer 最强区分

对比点 String StringBuffer StringBuilder
可变 ❌ 不可变 ✅ 可变 ✅ 可变
线程安全 安全 ✅ 安全(sync加锁) ❌ 不安全
效率 最低 中等 最高
底层 final char[] 动态char[] 动态char[]
循环拼接 极慢 较慢 最快
推荐场景 常量、固定文本 多线程拼接 单线程所有拼接

死记口诀
常量用String,并发用Buffer,日常全用Builder


三、创建字符串 对象数量 超级易错

  1. String s = "abc"
  • 池中有:0个新对象
  • 池中无:1个常量池对象
  1. String s = new String("abc")
  • 池中有:只在堆创建1个
  • 池中无:常量池1个 + 堆1个 = 2个

必考结论:new 最少1个,最多2个。


四、+拼接 编译期 & 运行期 分水岭

1)全常量拼接(编译期优化)

String s = "a"+"b"+"c";

编译直接变成 "abc"直接入常量池

2)含变量拼接(运行期)

String a = "a";
String s = a + "b";

底层new StringBuilder拼接,生成堆对象,不入常量池

判断口诀:全字面量=编译优化;有变量=堆新对象


五、== 与 equals 生死区别

  1. ==:比内存地址
  2. equals:String重写,比字符串内容
"abc" == new String("abc")false
"abc".equals(new String("abc"))true

业务开发一律用equals判相等


六、字符串常量池 位置变迁(面试必坑)

  • JDK1.6 及以前:方法区(永久代)
  • JDK1.7 ~ JDK17:移到堆 Heap
  • 目的:永久代内存不足,方便GC回收闲置字符串

七、intern() 方法 JDK6 vs JDK7+ 最大坑点

JDK1.6

池内没有 → 新建一份字符串放进永久代
堆对象 ≠ 池对象,地址不同

JDK1.7+(主流)

池内没有 → 直接把堆对象引用存入常量池
堆对象 == 池对象,地址完全一致

经典必考代码结论(JDK8)

String s = new String("1") + new String("1");
s.intern();
String s2 = "11";
System.out.println(s == s2); // true

调换顺序直接变false

String s2 = "11";
String s = new String("1") + new String("1");
s.intern();
System.out.println(s == s2); // false

八、高频误区黑名单(背完不踩坑)

  1. 误区:String不可变就不会产生垃圾
    正解:频繁拼接产生大量废弃字符串,极易造成内存浪费
  2. 误区:循环里直接用+拼接没事
    正解:循环内+会疯狂创建销毁Builder,性能雪崩
  3. 误区:intern()一定会节省内存
    正解:少量字符串没必要,海量重复字符串才适合用
  4. 误区:StringBuffer比StringBuilder快
    正解:相反!Builder无锁更快
  5. 误区:常量池所有字符串都会被永久保存
    正解:JDK7后在堆,闲置可被GC回收

九、最简答题万能模板

  1. 问不可变:private final char[] + 无修改方法
  2. 问拼接选谁:单线程StringBuilder,多线程StringBuffer
  3. 问new几个对象:字面量1个,new最多2个
  4. 问intern区别:6复制对象,7+存堆引用
  5. 问拼接原理:常量编译合并,变量底层Builder
相关文章
|
13小时前
|
运维 Ubuntu Linux
Linux 多发行版 远程桌面踩坑总结:Deepin / openKylin / Ubuntu 实战记录
本文详述TigerVNC在Ubuntu 26.04、Deepin 20.9/23.9及openKylin 2.0 SP2四大发行版的适配实践,重点解决Wayland/X11冲突、DBus、输入法、DDE兼容等痛点,最终推荐「deepin」为最稳定方案。(239字)
50 0
|
13小时前
|
人工智能 Rust 运维
qwen3.6-27b批处理掉Token后,​D​М‌X​Α‌РΙ补偿重试
Qwen3.6-27B是270亿参数稠密多模态模型,兼顾强大智能体编程能力与工程落地性:百万上下文、原生多模态、家用显卡可跑,API友好,稳定可控,完美平衡性能、成本与可运维性,成为中文业务场景首选生产级大模型。(239字)
|
26天前
|
人工智能 安全 API
深度解析 Claude Code 在 Prompt / Context / Harness 的设计与实践
文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。
2437 72
深度解析 Claude Code 在 Prompt / Context / Harness 的设计与实践
|
2月前
|
移动开发 前端开发 JavaScript
【贪吃蛇小游戏】 HTML (Canvas)+ JavaScript
这是一个基于 HTML5(Canvas)+JavaScript 开发的贪吃蛇小游戏,通过800×800画布实现蛇体绘制、食物生成、碰撞检测及方向控制,支持键盘操作与重新开始功能,代码结构清晰,适合初学者学习Web游戏开发。
872 11
|
11小时前
|
SQL 存储 缓存
【Java基础】核心关键字:final、static、volatile、synchronized、transient(附《思维导图》+《面试高频考点清单》)
本文系统解析Java五大核心关键字:final(不可变性)、static(类级共享)、volatile(轻量级可见性)、synchronized(重量级同步)与transient(序列化控制)。涵盖语义、使用场景、底层原理(内存屏障、Monitor、类加载机制等)、常见误区及高频面试要点。
【Java基础】核心关键字:final、static、volatile、synchronized、transient(附《思维导图》+《面试高频考点清单》)
|
10小时前
|
存储 安全 Java
【Java基础】泛型:泛型擦除、通配符、上下界限定(附《思维导图》+《面试高频考点清单》)
本文系统梳理了Java泛型的核心知识体系,主要内容包括: 泛型概述:介绍了泛型的定义、本质和三大优势(类型安全、代码复用、可读性),以及泛型类、接口和方法的三种使用形式。 泛型擦除:深入解析了Java泛型实现的核心机制,包括擦除规则(无界类型擦除为Object,有界类型擦除为第一个边界类型)、擦除带来的问题(如无法使用instanceof、创建泛型数组等)及其解决方案。 泛型通配符:详细讲解了三种通配符类型(无界通配符、上界通配符和下界通配符)的语法、语义和使用场景。
|
10小时前
|
Java 编译器 程序员
【Java基础】异常体系:Error vs Exception、受检/非受检异常、try-catch-finally、try-with-resources(附《思维导图》+《面试高频考点清单》)
本文系统梳理Java异常体系:以`Throwable`为根,分`Error`(JVM级不可恢复错误)与`Exception`(可处理异常);后者再分为编译期强制处理的**受检异常**(如`IOException`)和运行时抛出的**非受检异常**(如`NullPointerException`),并详解`try-catch-finally`、`try-with-resources`、异常链及最佳实践。
|
14小时前
|
Kubernetes 数据安全/隐私保护 容器
【架构实战】Helm Chart应用部署最佳实践
Helm是Kubernetes的包管理器: 核心概念: Chart:应用包 Repository:Chart仓库 Release:部署实例
24 2
|
13小时前
|
机器学习/深度学习 人工智能 算法
用好 Codex Goal,关键就这三步
Codex 新增 /goal 命令,支持目标驱动的Agent式循环:设定可量化目标(如“运行时间降20%且测试全通过”)、构建短反馈闭环、用PLAN/EXPERIMENTS等Markdown文件持久化记忆。三要素缺一不可,方能真正释放长任务自动化潜力。
用好 Codex Goal,关键就这三步
|
1月前
|
人工智能 缓存 前端开发
SDD-RIPER 团队落地指南:如何让整个团队在一周内跑通大模型编程
本文给你一套可执行的团队落地方案:从安装到试点到全面推开,一周内让整个团队跑通大模型编程,并且质量可控、效果可量化。(文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。)
1470 14