作者:没有四次元口袋的蓝胖
日期:2026-07-12
标签:Java, String
Java String知识梳理
一、String 的不可变性
String 是不可变的(immutable),一旦创建,其值就不能修改。
1.1 为什么设计成不可变?
原因:
- 线程安全:不可变对象天然线程安全,无需同步
- 字符串常量池:可以共享相同字符串,节省内存
- hashCode 缓存:创建时计算一次 hashCode,后续直接使用(HashMap 的 key)
- 安全性:作为参数传递时不会被篡改(数据库连接、网络地址等)
1.2 源码分析
publicfinalclassString{privatefinalcharvalue[];// final 修饰,引用不可变// Java 9+ 改为:private final byte[] value;publicString(Stringoriginal){this.value=original.value;// 直接复用// ...}}关键:
final class String:String 类不可继承final char[] value:数组引用不可变(但数组内容理论上可变,Java 通过不提供修改方法保证)
1.3 "不可变"的陷阱
Stringstr="hello";str=str+" world";// 看起来修改了,实际上创建了新对象// 实际过程:// 1. 创建 "hello"// 2. 创建 " world"// 3. 创建新字符串 "hello world"// 4. str 引用指向新字符串// 5. 原来的 "hello" 还在(可能被其他引用使用)💡 String 的"修改"都是创建新对象,原对象不变。
二、字符串常量池
2.1 常量池的作用
字符串常量池(String Pool)是 JVM 堆内存中的一块特殊区域,用于存储字符串字面量。
优点:
- 节省内存:相同字符串共享
- 提高效率:避免重复创建
2.2 创建方式的区别
// 方式1:字面量(常量池)Strings1="hello";// 方式2:new 对象(堆内存)Strings2=newString("hello");// 方式3:拼接(编译器优化)Strings3="hel"+"lo";// 编译期优化为 "hello"2.3 == vs equals
Strings1="hello";Strings2="hello";Strings3=newString("hello");Strings4=newString("hello");System.out.println(s1==s2);// true(同一个常量池对象)System.out.println(s1==s3);// false(s3 在堆中)System.out.println(s3==s4);// false(两个不同的对象)System.out.println(s1.equals(s3));// true(内容相同)规则:
==:比较引用(内存地址)equals():比较内容
⚠️String 必须用 equals() 比较内容,不能用 ==。
2.4 intern() 方法
intern()会将字符串放入常量池(如果不存在),并返回常量池中的引用。
Strings1=newString("hello");// 堆中创建Strings2=s1.intern();// 将 "hello" 放入常量池(如果不存在)Strings3="hello";// 从常量池获取System.out.println(s1==s2);// false(s1 在堆,s2 在常量池)System.out.println(s2==s3);// true(都是常量池中的 "hello")Java 6 vs Java 7+:
- Java 6:常量池在永久代(PermGen),intern() 会复制字符串
- Java 7+:常量池移到堆中,intern() 不会复制,只是添加引用
三、字符串拼接
3.1 拼接方式对比
| 方式 | 类型 | 线程安全 | 性能 | 适用场景 |
|---|---|---|---|---|
+ | 语法糖 | 安全 | 低(每次创建新对象) | 少量拼接 |
StringBuilder | 可变 | 不安全 | 高 | 单线程大量拼接 |
StringBuffer | 可变 | 安全 | 中(有同步开销) | 多线程拼接 |
String.concat() | 方法 | 安全 | 中 | 不常用 |
3.2 编译器优化
// 编译期常量拼接(优化)Strings1="hel"+"lo";// 编译为 "hello"// 运行期拼接(不优化)Stringa="hel";Stringb="lo";Strings2=a+b;// 运行期拼接,创建新对象// final 修饰(优化)finalStringa="hel";finalStringb="lo";Strings3=a+b;// 编译期优化为 "hello"3.3 StringBuilder vs StringBuffer
// StringBuilder(非线程安全,性能高)StringBuildersb1=newStringBuilder();sb1.append("hello");sb1.append(" ");sb1.append("world");Stringresult1=sb1.toString();// StringBuffer(线程安全,性能低)StringBuffersb2=newStringBuffer();sb2.append("hello");sb2.append(" ");sb2.append("world");Stringresult2=sb2.toString();底层实现:
// StringBuffer 的 append 方法(有 synchronized)publicsynchronizedStringBufferappend(Stringstr){super.append(str);returnthis;}// StringBuilder 的 append 方法(无 synchronized)publicStringBuilderappend(Stringstr){super.append(str);returnthis;}选择建议:
- 单线程:用 StringBuilder
- 多线程:用 StringBuffer
- 少量拼接:直接用
+
四、String 常见方法
4.1 常用方法速查
Stringstr="Hello World";// 长度str.length();// 11// 字符操作str.charAt(0);// 'H'str.toCharArray();// ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']// 截取str.substring(0,5);// "Hello"// 查找str.indexOf("World");// 6str.lastIndexOf("l");// 9str.contains("World");// truestr.startsWith("Hello");// truestr.endsWith("World");// true// 大小写str.toLowerCase();// "hello world"str.toUpperCase();// "HELLO WORLD"// 去除空白" hello ".trim();// "hello"// 替换str.replace("World","Java");// "Hello Java"str.replaceAll("[aeiou]","*");// "H*ll* W*rld"// 分割"a,b,c".split(",");// ["a", "b", "c"]// 比较str.equals("Hello World");// truestr.equalsIgnoreCase("hello world");// truestr.compareTo("Hello Java");// 正数(字典序)// 格式化String.format("Name: %s, Age: %d","Tom",20);// "Name: Tom, Age: 20"4.2 性能陷阱
// 错误:循环中用 + 拼接(每次都创建新对象)Stringresult="";for(inti=0;i<1000;i++){result+=i;// 创建 1000 个 String 对象}// 正确:用 StringBuilderStringBuildersb=newStringBuilder();for(inti=0;i<1000;i++){sb.append(i);}Stringresult=sb.toString();五、String 与其他类型转换
5.1 String ↔ 基本类型
// String → intintnum1=Integer.parseInt("123");// 123intnum2=Integer.valueOf("123");// 123// int → StringStrings1=String.valueOf(123);// "123"Strings2=Integer.toString(123);// "123"Strings3=123+"";// "123"(不推荐)// String → doubledoubled=Double.parseDouble("3.14");// String → booleanbooleanb=Boolean.parseBoolean("true");5.2 String ↔ 字符数组
// String → char[]char[]chars="hello".toCharArray();// char[] → StringStrings1=newString(chars);Strings2=String.valueOf(chars);// String → byte[](编码)byte[]bytes="你好".getBytes("UTF-8");// byte[] → String(解码)Strings3=newString(bytes,"UTF-8");六、常见面试题
6.1 String、StringBuffer、StringBuilder 的区别?
| 对比 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 安全(synchronized) | 不安全 |
| 性能 | 低(每次创建新对象) | 中 | 高 |
| 适用 | 少量操作 | 多线程拼接 | 单线程拼接 |
6.2 String 为什么是不可变的?
- 安全性:作为参数传递时不会被篡改
- 线程安全:不可变对象天然线程安全
- hashCode 缓存:创建时计算一次,HashMap 的 key 效率高
- 字符串常量池:可以共享相同字符串
6.3 String s = new String(“hello”) 创建了几个对象?
答案:可能创建 1 个或 2 个对象。
情况 1:常量池中已有 “hello”
- 只创建 1 个对象(堆中的 String 对象)
- “hello” 字面量在常量池,new 的对象在堆中
情况 2:常量池中没有 “hello”
- 创建 2 个对象
- 1 个在常量池(“hello” 字面量)
- 1 个在堆中(new String)
6.4 如何判断 String 是否为空?
// 错误:会 NPEif(str.equals("")){}// 推荐if(str==null||str.isEmpty()){}// Java 8if(str==null||str.length()==0){}// 兼容老版本// Java 11+if(str==null||str.isBlank()){}// 包含空白字符6.5 String.intern() 的作用?
- 检查常量池中是否存在该字符串
- 存在则返回常量池中的引用
- 不存在则将字符串放入常量池,返回引用
应用场景:
- 节省内存(大量重复字符串)
- 字符串比较(用 == 代替 equals)
七、思维导图速览
┌───────────────────────────────────────────────────┐ │ String 高频考点 │ ├───────────────────────────────────────────────────┤ │ │ │ 不可变性 │ │ • final class String │ │ • final char[] value │ │ • 原因:安全/线程安全/hashCode缓存/常量池 │ │ │ │ 常量池 │ │ • 字符串字面量存储在常量池 │ │ • new String() 在堆中创建 │ │ • intern() 放入常量池 │ │ • == vs equals │ │ │ │ 拼接 │ │ • +:语法糖,编译器优化 │ │ • StringBuilder:单线程,性能高 │ │ • StringBuffer:多线程,性能中 │ │ │ │ 常见方法 │ │ • length/charAt/substring/indexOf/contains │ │ • replace/replaceAll/split/trim │ │ • equals/equalsIgnoreCase/compareTo │ │ │ │ 面试题 │ │ • String vs StringBuffer vs StringBuilder │ │ • 为什么不可变 │ │ • new String("hello") 创建几个对象 │ │ • intern() 的作用 │ │ │ │ 陷阱 │ │ ✗ 用 == 比较 String │ │ ✗ 循环中用 + 拼接 │ │ ✗ str.equals("") 会 NPE │ │ ✓ 用 equals() 比较内容 │ │ ✓ 用 StringBuilder 拼接 │ │ ✓ str == null || str.isEmpty() 判空 │ └───────────────────────────────────────────────────┘八、写在最后
- String 不可变性是核心,要能说出 4 个原因和源码实现
- 字符串常量池是高频考点,要理解
new String()和字面量的区别 - StringBuilder vs StringBuffer要知道什么时候用哪个
- 常见陷阱:
==vsequals()、循环中用+拼接、空指针判断