String 是什么?—— 核心概念
String(字符串)是 Java 中用于表示文本数据的类型,它是一个对象,而不是像 int, double 这样的基本数据类型,这意味着你可以调用 String 对象的方法来操作字符串内容。

String greeting = "Hello, World!"; int length = greeting.length(); // 调用 length() 方法获取字符串长度 System.out.println(length); // 输出 13
String 的两大核心特性
理解 String 的关键在于掌握以下两个特性:
a) 不可变性
这是 String 最重要、最核心的特性,一旦一个 String 对象被创建,它的内容就不能被改变。
如何实现不可变性?
private final char[] value:String类内部使用一个final的char数组来存储字符。final关键字意味着这个数组引用一旦指向一个数组,就不能再指向其他数组。- 所有修改操作(如
substring,replace,concat等)都不会修改原始的String对象,它们会创建并返回一个新的String对象,原始对象保持不变。
示例:

String original = "hello";
// toUpperCase() 不会改变 original,而是返回一个新的字符串
String modified = original.toUpperCase();
System.out.println("Original: " + original); // 输出: Original: hello
System.out.println("Modified: " + modified); // 输出: Modified: HELLO
过程解析:
String original = "hello";在内存中创建了一个String对象,内容是 "hello"。original.toUpperCase();方法被调用,但这个方法不会去修改 "hello" 这个对象。- 相反,它会创建一个新的
String对象,内容是 "HELLO",并将这个新对象的引用赋给变量modified。 - 变量
original仍然指向最初的 "hello" 对象。
不可变性的优点:
- 线程安全不能被改变,所以多个线程可以同时访问一个
String对象而无需担心数据被意外修改,无需额外的同步措施。 - 安全性:在处理文件路径、数据库URL、网络地址等敏感信息时,不可变性可以防止这些信息在程序中被意外篡改。
- 性能优化不变,
String对象可以被共享和缓存,Java 有一个叫做 字符串常量池 的机制来优化内存使用。
b) 字符串常量池
这是一个特殊的内存区域,专门用于存储字符串字面量。
工作原理: 当你使用双引号 创建一个字符串时,JVM 首先会检查字符串常量池中是否已经存在一个完全相同的字符串。
- 如果存在:JVM 不会创建新对象,而是直接返回池中已有对象的引用。
- 如果不存在:JVM 会在池中创建这个字符串对象,然后返回它的引用。
示例:
String s1 = "hello"; String s2 = "hello"; // s1 和 s2 指向的是同一个对象 System.out.println(s1 == s2); // 输出 true (== 比较的是内存地址) System.out.println(s1.equals(s2)); // 输出 true (.equals() 比较的是内容)
使用 new 关键字创建 String:
如果你使用 new 关键字,则会强制在堆内存中创建一个新的 String 对象,不会检查也不使用字符串常量池。
示例:
String s3 = new String("hello");
String s4 = new String("hello");
// s3 和 s4 都是在堆上创建的新对象,它们彼此不同,也与池中的 "hello" 不同
System.out.println(s3 == s4); // 输出 false
System.out.println(s3.equals(s4)); // 输出 true
String 的常用方法
String 类提供了非常丰富的方法来处理字符串。
| 方法 | 描述 | 示例 |
|---|---|---|
length() |
返回字符串的长度。 | "abc".length() 返回 3 |
charAt(int index) |
返回指定索引处的字符。 | "abc".charAt(1) 返回 'b' |
substring(int beginIndex) |
返回从 beginIndex 开始到结尾的子字符串。 |
"hello".substring(1) 返回 "ello" |
substring(int beginIndex, int endIndex) |
返回从 beginIndex 开始到 endIndex (不包含) 的子字符串。 |
"hello".substring(1, 3) 返回 "el" |
indexOf(String str) |
返回子字符串 str 第一次出现的索引,如果不存在则返回 -1。 |
"hello world".indexOf("world") 返回 6 |
lastIndexOf(String str) |
返回子字符串 str 最后一次出现的索引。 |
"aabbcc".lastIndexOf("b") 返回 3 |
toUpperCase() / toLowerCase() |
将字符串转换为大写/小写。 | "Java".toUpperCase() 返回 "JAVA" |
trim() |
去除字符串首尾的空白字符(空格, tab, 换行等)。 | " hello ".trim() 返回 "hello" |
replace(char old, char new) / replace(CharSequence old, CharSequence new) |
替换所有匹配的字符或子串。 | "hello".replace('l', 'p') 返回 "heppo" |
split(String regex) |
根据给定的正则表达式分割字符串,返回一个字符串数组。 | "a,b,c".split(",") 返回 ["a", "b", "c"] |
startsWith(String prefix) / endsWith(String suffix) |
判断字符串是否以指定前缀/后缀开头/ | "filename.txt".endsWith(".txt") 返回 true |
equals(Object anObject) |
比较两个字符串的是否相等。 | "abc".equals("abc") 返回 true |
equalsIgnoreCase(String anotherString) |
比较两个字符串的内容是否相等,忽略大小写。 | "ABC".equalsIgnoreCase("abc") 返回 true |
compareTo(String anotherString) |
按字典顺序比较两个字符串,返回负数、零或正数。 | "apple".compareTo("banana") 返回负数 |
vs .equals() 的区别
这是一个非常经典的面试题。
- 比较的是两个对象的内存地址(引用是否相同)。
.equals():String类重写了Object的equals()方法,它比较的是两个字符串的是否相同。
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true (都指向池中的同一个对象)
System.out.println(s1.equals(s2)); // true (内容相同)
System.out.println(s1 == s3); // false (s3 在堆中是新对象)
System.out.println(s1.equals(s3)); // true (内容仍然相同)
String、StringBuilder 和 StringBuffer
当你在循环中频繁拼接字符串时,直接使用 号会非常低效,因为每次拼接都会创建一个新的 String 对象。
低效示例:
String result = "";
for (int i = 0; i < 1000; i++) {
result = result + i; // 每次循环都创建一个新的 String 对象
}
这会导致大量的内存分配和垃圾回收,性能很差。
这时,你应该使用 StringBuilder 或 StringBuffer。
StringBuilder
- 特点:可变的、非线程安全的。
- 性能:因为它没有同步开销,所以它的拼接操作(
append())速度最快。 - 适用场景:单线程环境下进行大量的字符串修改操作,例如在循环中拼接字符串,这是最常用的选择。
高效示例:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // 在原有的对象上进行修改,不创建新对象
}
String result = sb.toString(); // 最后一次性转换为 String
StringBuffer
- 特点:可变的、线程安全的。
- 性能:它的方法(如
append())都使用了synchronized关键字进行同步,以保证线程安全,它的性能比StringBuilder稍慢。 - 适用场景:多线程环境下,如果多个线程需要同时修改同一个字符串缓冲区。
如何选择?
- 默认使用
StringBuilder:在绝大多数情况下,我们都在单线程中工作,StringBuilder的性能优势是明显的。 - 只有在明确需要线程安全时才使用
StringBuffer:在 Web 应用中处理共享的请求或响应数据时。
| 特性 | 描述 |
|---|---|
| 本质 | String 是一个 final 类,代表一个字符序列。 |
| 不可变性 | String 对象创建后其内容不可改变,所有修改操作都返回新对象。 |
| 字符串常量池 | 优化内存机制,存储字符串字面量,避免重复创建相同内容的字符串。 |
| 创建方式 | String s = "hello"; (可能使用池) vs String s = new String("hello"); (强制在堆上创建) |
| 常用方法 | length(), charAt(), substring(), indexOf(), replace(), split(), equals() 等。 |
vs equals() |
比较地址,equals() 比较内容。 |
| 性能优化 | 频繁修改字符串时,使用 StringBuilder (单线程) 或 StringBuffer (多线程) 代替 拼接。 |
掌握 String 的这些核心概念,对于编写高效、健壮的 Java 代码至关重要。
