Java String 类深入解析
一、前言
在 Java 开发中,String 字符串是使用频率最高的类之一,无论是日常业务开发、数据处理还是框架源码,都离不开 String。但很多开发者只停留在 new String()、equals()、concat() 等基础用法上,对其底层原理、内存机制、性能优化一知半解,极易写出 Bug 或低效代码。
本文将从源码结构、不可变性、内存存储、常用方法解析、字符串拼接、常见面试点六个维度,全面剖析 Java String 类,帮你从根源吃透字符串。
二、String 类基础定义与源码结构
1. 类定义
String 是 java.lang 包下的核心类,被 final 修饰,不能被继承,实现了三个核心接口:
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// 源码核心部分
}
Serializable:支持序列化,方便网络传输、持久化存储Comparable<String>:支持字符串自然排序CharSequence:字符序列接口,与StringBuilder、StringBuffer统一规范
2. 核心成员变量
// 用于存储字符串的字符数组,final 修饰,不可修改
private final char value[];
// 字符串的哈希码,缓存哈希值,避免重复计算
private int hash;
关键结论:
String的本质是一个不可变的 char 数组,所有字符串操作都不会修改原对象,而是生成新对象。
三、String 不可变性:核心特性与底层原理
1. 什么是不可变性?
一旦 String 对象被创建,其字符内容、长度、哈希值都无法被修改。
任何对字符串的操作(截取、替换、拼接),都会返回新的 String 对象,原对象保持不变。
示例代码:
public class StringImmutableTest {public static void main(String[] args) {String str = "Hello";// 看似修改,实际返回新对象str = str.replace("H", "h"); System.out.println(str); // 输出 hello}
}
执行 replace 后,原 "Hello" 对象并未改变,只是 str 引用指向了新的 "hello" 对象。
2. 不可变性的实现原因
- 类被 final 修饰:禁止继承,避免子类破坏不可变性
- 存储字符的 value 数组被 final 修饰:数组引用无法指向新内存
- value 数组被 private 修饰:外部无法直接访问修改数组元素
- 所有方法都不修改原数组:替换、截取等操作均新建数组 + 新对象
3. 不可变性的优势
- 线程安全:多线程环境下无需加锁,直接使用
- 字符串常量池复用:节省内存,提升性能
- 哈希值缓存:
HashMap、HashSet中作为 Key 效率极高 - 安全可靠:避免敏感字符串被意外 / 恶意修改
四、String 对象创建方式与内存存储
String 有两种创建方式,内存位置、性能、复用性完全不同,这是面试和开发必考点。
1. 字面量创建
String str1 = "Java";
- 直接在字符串常量池(String Constant Pool) 中创建对象
- 若常量池已存在
"Java",直接返回引用,不会新建对象 - 内存复用,性能最优,无冗余对象
2. new String () 创建
String str2 = new String("Java");
- 第一步:检查常量池,无
"Java"则在常量池创建 - 第二步:在堆内存中新建 String 对象,指向常量池的字符数组
- 无论常量池是否有值,堆中一定会创建新对象
- 产生冗余对象,不推荐使用
3. 两种创建方式对比
public class StringCreateTest {public static void main(String[] args) {String str1 = "Java";String str2 = "Java";String str3 = new String("Java");String str4 = new String("Java");// true :常量池复用,同一引用System.out.println(str1 == str2); // false :堆中不同对象System.out.println(str3 == str4); // false :str1(常量池),str3(堆)System.out.println(str1 == str3); }
}
== 与 equals () 区别:
==:基本类型比较值,引用类型比较内存地址String.equals():重写了方法,比较字符串内容,开发中判断字符串相等必须用equals()
五、String 常用方法源码 + 代码分析
1. equals () 方法:内容比较
// 源码简化版
public boolean equals(Object anObject) {// 1. 引用相同,直接返回 trueif (this == anObject) {return true;}// 2. 判断是否为 String 类型if (anObject instanceof String) {String anotherString = (String)anObject;int n = value.length;// 3. 长度相同,再逐字符比较if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}// 4. 类型/长度/字符不同,返回 falsereturn false;
}
使用示例:
String a = "hello";
String b = new String("hello");
// true,比较内容而非地址
System.out.println(a.equals(b));
2. hashCode () 方法:哈希值计算
// 源码:哈希公式 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;
}
- 选用 31 :质数,减少哈希冲突,JVM 可优化(
31*i = (i<<5)-i) - 哈希值缓存:计算一次后永久缓存,提升
HashMap性能
3. 其他高频常用方法
public class StringMethodTest {public static void main(String[] args) {String str = " Hello Java ";// 1. 去除首尾空格System.out.println(str.trim()); // Hello Java// 2. 获取长度System.out.println(str.length()); // 13// 3. 截取子串 [begin, end)System.out.println(str.substring(2, 7)); // Hello// 4. 转大写/小写System.out.println(str.toUpperCase()); // HELLO JAVA // 5. 判断是否包含子串System.out.println(str.contains("Java")); // true// 6. 字符串分割String[] arr = "a,b,c".split(",");// 7. 字符替换"Hello".replace('l', 'x');}
}
六、字符串拼接:原理与性能坑点
字符串拼接是开发中最常用操作,但不同方式性能天差地别。
1. 直接 + 拼接
String a = "Hello" + " Java";
- 编译期优化:直接合并为
"Hello Java",存入常量池 - 变量拼接:
a + b底层会自动创建StringBuilder调用append()
2. 循环中 + 拼接
// 低效代码:循环创建大量 StringBuilder 对象
String s = "";
for (int i = 0; i < 1000; i++) {s += i;
}
问题:每次循环都 new StringBuilder() → append() → toString(),产生大量临时对象,内存飙升、性能极差。
3. 最优拼接方案
- 单线程 / 非线程安全:
StringBuilder(JDK 1.5+,性能最高) - 多线程 / 线程安全:
StringBuffer(方法加synchronized)
高效代码示例:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {sb.append(i);
}
String result = sb.toString();
七、String、StringBuffer、StringBuilder 对比(面试必考)
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 不可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全 | 安全(synchronized) | 不安全 |
| 性能 | 低 | 中 | 高 |
| 使用场景 | 少量字符串操作 | 多线程拼接 | 单线程高频拼接 |
总结:
- 固定字符串:用
String - 高频拼接:单线程用
StringBuilder,多线程用StringBuffer
八、总结
- String 是不可变类:底层是 final char 数组,所有操作生成新对象
- 创建方式:字面量(常量池,推荐)、
new String()(堆,慎用) - 比较相等:必须用
equals(),==比较地址 - 拼接性能:循环拼接用
StringBuilder,禁止用+ - 核心价值:不可变性保证了安全、高效、线程安全,是 Java 字符串设计的精髓
