HoRain云--Java String类:不可变设计的深度解析
🎬 HoRain云小助手:个人主页
🔥 个人专栏: 《Linux 系列教程》《c语言教程》
⛺️生活的理想,就是为了理想的生活!
⛳️ 推荐
前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。
专栏介绍
专栏名称 | 专栏介绍 |
《C语言》 | 本专栏主要撰写C干货内容和编程技巧,让大家从底层了解C,把更多的知识由抽象到简单通俗易懂。 |
《网络协议》 | 本专栏主要是注重从底层来给大家一步步剖析网络协议的奥秘,一起解密网络协议在运行中协议的基本运行机制! |
《docker容器精解篇》 | 全面深入解析 docker 容器,从基础到进阶,涵盖原理、操作、实践案例,助您精通 docker。 |
《linux系列》 | 本专栏主要撰写Linux干货内容,从基础到进阶,知识由抽象到简单通俗易懂,帮你从新手小白到扫地僧。 |
《python 系列》 | 本专栏着重撰写Python相关的干货内容与编程技巧,助力大家从底层去认识Python,将更多复杂的知识由抽象转化为简单易懂的内容。 |
《试题库》 | 本专栏主要是发布一些考试和练习题库(涵盖软考、HCIE、HRCE、CCNA等) |
目录
⛳️ 推荐
专栏介绍
☕ Java String类:不可变的那点事
🎯 先立骨架:String 是什么
🧱 创建方式:两种路径,内存位置不同
常量池是怎么回事
⚖️ ==vs equalsvs compareTo
🔤 常用 API(挑高频)
🏗️ 拼接:+vs StringBuildervs StringBuffer
+号在循环里是性能杀手
StringBuildervs StringBuffer
🧬 不可变的好处(为什么 Java 这么设计)
🔍 几个高频追问点
1)String s = null;vs ""
2)switch能用 String(Java 7+)
3)String.format
4)intern()什么时候用
🧮 编码相关(历史包袱)
🆕 JDK 9 之后两个变动(面试爱问)
🎯 一张表收尾
☕ JavaString类:不可变的那点事
String是 Java 里最特殊也最常被追问的类——看似简单,牵扯到不可变设计、常量池、编码演进、拼接性能一堆东西。
🎯 先立骨架:String 是什么
public final class String implements Serializable, Comparable<String>, CharSequence三个关键事实:
final类:不能被继承不可变(immutable):值存在
private final byte[] value里,外部改不了char[] → byte[](JDK 9+):从"每个 char 2 字节 UTF-16"改成"byte[] + coder 标志",拉丁文省一半内存
🧱 创建方式:两种路径,内存位置不同
// 1. 字面量 → 常量池(String Pool) String s1 = "hello"; // 2. new → 堆里新对象(顺带常量池放一份,但不保证) String s2 = new String("hello");常量池是怎么回事
String a = "hello"; String b = "hello"; System.out.println(a == b); // true,同一池对象 String c = new String("hello"); String d = new String("hello"); System.out.println(c == d); // false,不同堆对象 System.out.println(c.equals(d)); // true,值相等 // intern():拉到池里,池里有就返回池对象 String e = new String("hello").intern(); System.out.println(a == e); // true💡
new String("hello")最坏情况会创建 2 个对象:池里一份(如果之前没出现过)+ 堆里一份。所以日常没必要new String("xxx")。
⚖️==vsequalsvscompareTo
String s1 = new String("abc"); String s2 = new String("abc"); s1 == s2 // false,比地址 s1.equals(s2) // true,比值(先比地址,再比长度,再逐 char) s1.equalsIgnoreCase("ABC") // true,忽略大小写 // 比大小 / 排序 "abc".compareTo("abd") // -1(按字典序,Unicode 值) "Abc".compareToIgnoreCase("abc") // 0⚠️
equals传null返回false;Objects.equals(s1, s2)更安全(俩都 null 也返回 true)。
🔤 常用 API(挑高频)
String s = " Hello, 世界 "; s.length(); // 9(char 数,注意不是字节数) s.charAt(1); // 'H' s.trim(); // "Hello, 世界"(去首尾空白,Unicode 空白不全) s.strip(); // "Hello, 世界"(Java 11+,按 Unicode 空白,更准) s.stripLeading(); s.stripTrailing(); s.toLowerCase(); s.toUpperCase(); s.startsWith(" He"); // true s.endsWith(" "); // true s.substring(2, 7); // "Hello"(左闭右开) s.replace("l", "L"); // " HeLLo, 世界 " s.replaceAll("\\s+", ""); // 正则替换:"Hello,世界"(中间逗号还在) String[] parts = s.split(","); // [" Hello", " 世界 "] String.join("-", "a", "b", "c"); // "a-b-c" // 判空(Java 11+) s.isEmpty(); // false s.isBlank(); // false(" " → true,比 isEmpty 多认空白)🏗️ 拼接:+vsStringBuildervsStringBuffer
+号在循环里是性能杀手
// ❌ 每次循环 new 一个 StringBuilder + toString(),O(n²) String s = ""; for (int i = 0; i < 10000; i++) { s += i; // 等效:s = new StringBuilder(s).append(i).toString(); } // ✅ 一次 StringBuilder,O(n) StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append(i); } String s = sb.toString();📌 单条
"a" + "b" + "c"编译器(javac)已经在编译期合并成"abc"(常量折叠),或多条非循环拼接也会帮你包一层StringBuilder——只有循环里的+才是真坑。
StringBuildervsStringBuffer
类 | 线程安全 | 性能 | 场景 |
|---|---|---|---|
| ❌ 不同步 | 快 | 单线程(99% 场景) |
| ✅ | 慢点 | 多线程共享(少见) |
🧬 不可变的好处(为什么 Java 这么设计)
线程安全:天然不可变,随便传
常量池复用:省内存
hash 可缓存:
hashCode()算一次存起来(看源码hash字段)安全:类加载器用 String 当类名/路径,不可变防篡改
代价:频繁修改产生大量中间对象 → 所以用StringBuilder。
🔍 几个高频追问点
1)String s = null;vs""
String a = null; // 没对象,调 a.length() → NPE String b = ""; // 有对象,长度 02)switch能用 String(Java 7+)
switch (s) { case "hello" -> System.out.println("hi"); case "world" -> System.out.println("earth"); default -> {} }原理:编译期转成hashCode() + equals的 if-else,不是 JVM 层面支持的。
3)String.format
String s = String.format("name=%s, age=%d, pi=%.2f", "Alice", 30, Math.PI); // "name=Alice, age=30, pi=3.14"4)intern()什么时候用
几乎不用。只有在你从别处(网络/文件)读了一堆重复字符串、想手动挤到池里省内存时才考虑:
String s = readFromNet(); // 可能重复百万个 s = s.intern(); // 池里复用代价:池在 Perm/Metaspace,早期 JDK 容易 OOM,现在没那么脆,但仍别滥用。
🧮 编码相关(历史包袱)
// char 是 UTF-16,一个 char ≠ 一个"字" String s = "😀"; // emoji,占 2 个 char(代理对) System.out.println(s.length()); // 2(char 数) System.out.println(s.codePointCount(0, s.length())); // 1(真正的"字形"数) // 字节 ↔ 字符 byte[] utf8 = s.getBytes(StandardCharsets.UTF_8); // 指定编码,别用无参(跟平台走) String back = new String(utf8, StandardCharsets.UTF_8);⚠️ 无参
getBytes()用平台默认编码,Windows 中文环境曾默认 GBK,跨环境就炸——永远显式传Charset。
🆕 JDK 9 之后两个变动(面试爱问)
char[]→byte[] + coderLATIN1:1 字节存一个 charUTF16:2 字节纯英文字符串内存直接砍半
String拼接在 javac 层单条 / 非循环多段
+→ 编译期直接StringBuilder(或常量折叠)循环里
+仍每轮 new → 自己写StringBuilder
🎯 一张表收尾
问题 | 答案 |
|---|---|
String 可变吗 | ❌ final + private final byte[] |
| 编译期折成 |
| 池 1 + 堆 1(池已有时堆 1) |
循环拼接用啥 |
|
| 地址 vs 值 |
为什么不可变 | 线程安全 / 池复用 / hash 缓存 / 安全 |
char 能表示所有 Unicode 吗 | ❌,增补字符(emoji)要俩 char |
要不要接着看StringBuilder源码那点事,或者String、StringBuilder、StringBuffer的面试连招题?
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙
