String 是什么?
在 Java 中,String(字符串)是一个用来表示字符序列的对象,任何用双引号 括起来的文本都是一个 String 对象。

String greeting = "Hello, World!"; String name = "Alice"; String emptyString = ""; // 空字符串
String 的关键特性
理解 String 的特性是掌握它的关键,主要有以下五点:
不可变性
这是 String 最重要的特性,一个 String 对象在创建之后,其内容(即它所指向的字符序列)是不可改变的。
-
如何实现不可变性?
String类被final修饰,意味着它不能被继承,从而没有人能通过创建子类来改变它的行为。- 其内部存储字符的数组
private final char value[];也被final修饰,并且没有提供public的修改方法(如set())。 - 所有修改字符串内容的方法(如
substring(),replace(),concat()等)实际上都不会修改原有的字符串对象,而是创建并返回一个新的String对象来保存修改后的结果。
-
示例:
(图片来源网络,侵删)String s1 = "hello"; String s2 = s1.toUpperCase(); // s1 没有改变,s2 指向一个新的 "HELLO" 对象 System.out.println(s1); // 输出: hello System.out.println(s2); // 输出: HELLO s1 = s1 + " world"; // s1 指向了一个新的 "hello world" 对象,原来的 "hello" 对象等待被回收 System.out.println(s1); // 输出: hello world
-
不可变性的好处:
- 线程安全不可变,所以多个线程可以同时访问一个
String对象而无需进行同步,不会出现数据竞争问题。 - 安全:在 Java 中,
String被广泛用于存储密码、文件路径等敏感信息,不可变性确保了这些信息在创建后不会被意外或恶意地修改。 - 性能优化:字符串是不可变的,所以可以安全地被共享和缓存,Java 虚拟机为了优化字符串的性能,引入了一个特殊的内存区域——字符串常量池。
- 线程安全不可变,所以多个线程可以同时访问一个
字符串常量池
这是一个位于堆内存中的特殊缓存区,专门用来存储字符串字面量()。
-
工作原理:
- 当你使用
String s = "hello";这种方式创建字符串时,JVM 首先会检查字符串常量池中是否已经存在"hello"这个字符串。 - 如果存在,则直接返回池中该字符串的引用,不会创建新对象。
- 如果不存在,则会在池中创建一个新的
"hello"对象,然后将其引用返回。
- 当你使用
-
与
new关键字的区别: 使用new关键字创建字符串(String s = new String("hello");)时,JVM 不会检查字符串常量池,而是直接在堆内存中创建一个新的对象,即使池中已经存在相同内容的字符串。 -
示例:
String s1 = "hello"; // 在常量池中创建 "hello" String s2 = "hello"; // 直接从常量池中引用 "hello",s1 和 s2 指向同一个对象 System.out.println(s1 == s2); // 输出: true (比较的是内存地址) System.out.println(s1.equals(s2)); // 输出: true (比较的是内容) String s3 = new String("hello"); // 在堆中创建一个新的 "hello" 对象 System.out.println(s1 == s3); // 输出: false (s1指向池,s3指向堆) System.out.println(s1.equals(s3)); // 输出: true (内容相同)
与 char[] 的关系
String 的内部实现是基于一个 char 数组的,这个数组是 private final 的,保证了字符串内容的封装性和不可变性。
// 这是 String 类内部的一个简化版结构
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
// ... 其他代码
}
广泛使用
String 是 Java 中最常用的类之一,几乎所有程序都会用到它,它被用于:
- 存储用户输入、文件内容、网络数据等。
- 作为方法参数和返回值。
- 在日志、调试和显示信息中扮演核心角色。
性能考量
由于字符串的不可变性,在循环中进行大量的字符串拼接操作(如 )可能会造成性能问题,因为每次拼接都会创建一个新的 String 对象,增加 GC(垃圾回收)的压力。
不推荐的做法:
String result = "";
for (int i = 0; i < 1000; i++) {
result += "a"; // 每次循环都创建一个新的 String 对象
}
推荐的做法:
对于频繁的字符串修改操作,应使用 StringBuilder 或 StringBuffer,它们内部使用可变的字符数组,修改成本极低。
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("a"); // 在原有对象上进行修改,高效
}
String result = sb.toString();
StringBuilder:非线程安全,但性能更高,单线程环境下推荐使用。StringBuffer:线程安全,方法大多被synchronized修饰,性能稍差,多线程环境下使用。
String 的常用方法
| 方法 | 描述 | 示例 |
|---|---|---|
length() |
返回字符串的长度。 | "abc".length() -> 3 |
charAt(int index) |
返回指定索引处的字符。 | "hello".charAt(1) -> 'e' |
substring(int beginIndex) |
返回从 beginIndex 开始到结尾的子字符串。 |
"HelloWorld".substring(5) -> "World" |
substring(int begin, int end) |
返回从 begin 到 end-1 的子字符串。 |
"HelloWorld".substring(0, 5) -> "Hello" |
indexOf(String str) |
返回子字符串 str 第一次出现的索引,未找到则返回 -1。 |
"Hello".indexOf("ll") -> 2 |
lastIndexOf(String str) |
返回子字符串 str 最后一次出现的索引。 |
"HelloHello".lastIndexOf("l") -> 9 |
toUpperCase() / toLowerCase() |
将字符串转换为大写/小写,并返回新字符串。 | "aBc".toUpperCase() -> "ABC" |
trim() |
去除字符串两端的空白字符,并返回新字符串。 | " 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"] |
equals(Object anObject) |
是否相等。 | "abc".equals("ABC") -> false |
equalsIgnoreCase(String anotherString) |
忽略大小写是否相等。 | "abc".equalsIgnoreCase("ABC") -> true |
| 比较内存地址是否相等(是否是同一个对象)。 | s1 == s2 (如果都指向常量池中的"hello") -> true |
|
compareTo(String anotherString) |
按字典顺序比较两个字符串。 | "abc".compareTo("abd") -> -1 (因为 'c' < 'd') |
| 特性 | 描述 | 影响 |
|---|---|---|
| 不可变性 | 不可变。 | 线程安全、安全、利于字符串常量池优化。 |
| 字符串常量池 | 特殊的缓存区,存储字符串字面量。 | 节省内存,"abc" == "abc" 可能为 true。 |
final 类 |
不能被继承。 | 保证 String 内部实现的稳定性和不可变性。 |
基于 char[] |
内部实现是字符数组。 | 提供了高效的字符序列操作。 |
理解 String 的这些核心特性和常用方法,是进行 Java 开发的基础,记住它的不可变性和字符串常量池机制,是写出高效、健壮代码的关键。
