在 Java 中,String 是最常用的核心类之一,用于表示不可变的字符序列,属于 java.lang 包(无需手动导入)。以下从核心特性、常用操作、内存原理、常见陷阱等维度全面解析:
一、核心特性
1. 不可变性(Immutable)
String 对象一旦创建,其内部的字符数组(char[] value,Java 9+ 改为 byte[] 以节省内存)就无法修改。所有看似修改的操作(如 substring、replace)都会返回新的 String 对象,原对象保持不变。
String s = "hello";
s += " world"; // 实际创建了新对象,原"hello"仍存在
System.out.println(s); // 输出:hello world
不可变的好处:
- 线程安全:多线程访问无需同步;
- 可哈希:
hashCode缓存(计算一次后永久不变),适合作为 HashMap 键; - 字符串常量池复用。
2. 字符串常量池(String Pool)
JVM 为优化内存,在方法区(元空间) 中维护一个字符串常量池:
- 直接赋值(
String s = "abc"):先检查常量池,存在则复用引用,不存在则创建新对象放入常量池; new String("abc"):会创建两个对象(常量池的 "abc" + 堆中的 String 对象),堆对象引用常量池的字符数组。
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1 == s2); // true(常量池同一对象)
System.out.println(s1 == s3); // false(堆 vs 常量池)
System.out.println(s1.equals(s3)); // true(内容相等)
3. 常用创建方式
| 方式 | 说明 | 示例 |
|---|---|---|
| 直接赋值 | 优先使用常量池,效率最高 | String s = "java"; |
new String() | 堆中创建新对象,避免使用(除非特殊场景) | String s = new String("java"); |
String.valueOf() | 安全转换基本类型 / 对象(避免 null) | String s = String.valueOf(123); |
| 字符数组构造 | 从字符数组创建 | char[] arr = {'j','a','v','a'}; String s = new String(arr); |
二、常用方法(高频)
| 方法 | 功能 | 示例 |
|---|---|---|
length() | 获取字符串长度 | "java".length(); // 4 |
charAt(int index) | 获取指定索引字符 | "java".charAt(1); // 'a' |
equals(Object obj) | 比较内容(区分大小写) | "Java".equals("java"); // false |
equalsIgnoreCase() | 忽略大小写比较 | "Java".equalsIgnoreCase("java"); // true |
contains(CharSequence s) | 判断是否包含子串 | "hello".contains("ell"); // true |
indexOf(String s) | 查找子串首次出现索引(无则返回 -1) | "hello".indexOf("l"); // 2 |
lastIndexOf(String s) | 查找子串最后出现索引 | "hello".lastIndexOf("l"); // 3 |
substring(int start) | 从 start 截取到末尾 | "hello".substring(2); // "llo" |
substring(int start, int end) | 截取 [start, end) 区间 | "hello".substring(1,3); // "el" |
trim() | 去除首尾空白(Java 11+ 用 strip() 更通用) | " java ".trim(); // "java" |
replace(CharSequence old, CharSequence new) | 替换所有匹配子串 | "java".replace("a", "o"); // "jovo" |
split(String regex) | 按正则分割字符串 | "a,b,c".split(","); // ["a","b","c"] |
toLowerCase()/toUpperCase() | 转小写 / 大写 | "Java".toLowerCase(); // "java" |
isEmpty()/isBlank() | 判断空字符串(Java 11+ isBlank() 包含空白) | " ".isBlank(); // true |
concat(String str) | 拼接字符串(等价于 +,但效率低) | "a".concat("b"); // "ab" |
三、内存与性能优化
1. 避免频繁拼接(+ 号)
循环中使用 + 拼接字符串会创建大量临时对象,效率极低。推荐使用 StringBuilder(非线程安全)或 StringBuffer(线程安全):
// 低效
String s = "";
for (int i = 0; i < 1000; i++) {
s += i; // 每次创建新 String
}
// 高效
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // 仅操作一个字符数组
}
String result = sb.toString();
2. intern() 方法
手动将堆中的 String 对象加入常量池,复用引用:
String s1 = new String("java");
String s2 = s1.intern(); // 将 s1 内容加入常量池并返回常量池引用
String s3 = "java";
System.out.println(s2 == s3); // true
适用场景:大量重复字符串(如数据库返回的重复字段),减少内存占用。
3. Java 9 优化:byte[] 替代 char[]
Java 9 前,String 用 char[] 存储(每个字符占 2 字节);Java 9 后,根据字符编码自动选择 byte[](Latin-1 占 1 字节,UTF-16 占 2 字节),节省内存。
四、常见陷阱
1. == vs equals()
==:比较对象引用(是否指向同一内存地址);equals():比较字符串内容(String 重写了equals()方法)。
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false(不同对象)
System.out.println(s1.equals(s2)); // true(内容相同)
2. 空指针风险
调用 null 的 String 方法会抛出 NullPointerException,建议先判空:
String s = null;
// 错误:NPE
// s.equals("abc");
// 正确:常量在前,或先判空
"abc".equals(s); // false
Objects.nonNull(s) && s.equals("abc"); // 更严谨
3. substring() 内存泄漏(Java 7 前)
Java 7 前,substring() 会复用原字符串的 char[],即使截取小片段,也会持有原大字符串的引用,导致内存泄漏。Java 7+ 已修复(创建新的 char[])。
五、总结
String是不可变的,所有修改操作返回新对象;- 优先使用直接赋值(常量池),避免
new String(); - 拼接字符串用
StringBuilder(单线程)/StringBuffer(多线程); - 比较内容用
equals(),比较引用用==; - 大量重复字符串用
intern()优化内存。
1925

被折叠的 条评论
为什么被折叠?



