字符串
- 1. 字符串的创建
- 2. 字符串的不可变性
- 3. 字符串池(String Pool)
- 4. String 类的常用方法
- (1) `length()`
- (2) `charAt(int index)`
- (3) `substring(int start)` 和 `substring(int start, int end)`
- (4) `toUpperCase()` 和 `toLowerCase()`
- (5) `trim()`
- (6) `equals(Object obj)` 和 `equalsIgnoreCase(String anotherString)`
- (7) `indexOf(String str)` 和 `lastIndexOf(String str)`
- (8) `replace()` 和 `replaceAll()`
- (9) `split(String regex)`
- 5. 字符串拼接
- 6. 字符串与其他类型的转换
- 7. String.format()
- 8. 字符串的内存管理
- 9. 性能优化:StringBuilder 和 StringBuffer
- 10. String.format()
- 11. `intern()` 方法
- 总结
在 Java 中, 字符串(String)是用来表示字符序列的对象。Java 中的字符串是不可变的(immutable),也就是说,一旦字符串被创建,它的内容就不能再修改。这种设计使得字符串更加安全和高效。
1. 字符串的创建
Java 中的字符串有两种主要的创建方式:
(1) 字面量创建
通过字符串常量(字面量)直接赋值创建字符串:
String str1 = "Hello, World!";
这种方式会首先检查字符串常量池中是否已经存在相同内容的字符串,如果存在,则返回已有的字符串引用;如果不存在,则在常量池中创建一个新的字符串并返回。
(2) 使用 new
关键字
通过 new
关键字创建字符串对象时,会在堆内存中创建一个新的字符串对象,即使常量池中已经有相同的字符串:
String str2 = new String("Hello, World!");
这种方式始终会创建一个新的对象,而不会复用常量池中的字符串。
2. 字符串的不可变性
在 Java 中,字符串(String
)是不可变的,这意味着一旦字符串对象被创建,它的内容不能被修改。每次对字符串进行修改操作时,实际上都会生成一个新的字符串对象,而原有的字符串对象保持不变。
例如:
String str = "Hello";
str = str + " World";
System.out.println(str); // 输出 "Hello World"
在上面的代码中,虽然看似对 str
进行了修改,但实际上执行的是以下步骤:
- 创建了一个新的字符串
"Hello World"
。 str
引用被更新为指向新的字符串对象。
这保证了字符串的安全性与线程安全性(字符串不可变性使得多个线程访问同一字符串时无需担心修改的问题)。
3. 字符串池(String Pool)
Java 有一个字符串池(String Pool),也叫常量池,用于存储常量字符串。它的目的是提高内存利用效率。对于字面量创建的字符串,JVM 会先检查池中是否有相同的字符串,如果有,则直接引用池中的字符串,否则将其添加到池中。
例如:
String str1 = "Java";
String str2 = "Java";
System.out.println(str1 == str2); // 输出 true,因为它们引用的是同一个对象
但是,通过 new
创建的字符串对象不会使用池中的实例:
String str1 = new String("Java");
String str2 = new String("Java");
System.out.println(str1 == str2); // 输出 false,因为它们是不同的对象
这就是为什么使用字面量时,字符串更高效,因为它避免了重复创建相同内容的字符串。
4. String 类的常用方法
(1) length()
返回字符串的长度。
String str = "Java";
System.out.println(str.length()); // 输出 4
(2) charAt(int index)
返回指定位置的字符。
String str = "Hello";
System.out.println(str.charAt(1)); // 输出 'e'
(3) substring(int start)
和 substring(int start, int end)
返回指定区间的子字符串。substring(int start)
返回从 start
开始到字符串末尾的部分,substring(int start, int end)
返回从 start
到 end
-1 的部分。
String str = "Hello, World!";
System.out.println(str.substring(7)); // 输出 "World!"
System.out.println(str.substring(0, 5)); // 输出 "Hello"
(4) toUpperCase()
和 toLowerCase()
将字符串转换为全大写或全小写。
String str = "hello";
System.out.println(str.toUpperCase()); // 输出 "HELLO"
System.out.println(str.toLowerCase()); // 输出 "hello"
(5) trim()
去除字符串两端的空白字符(包括空格、制表符等)。
String str = " Hello, World! ";
System.out.println(str.trim()); // 输出 "Hello, World!"
(6) equals(Object obj)
和 equalsIgnoreCase(String anotherString)
比较字符串内容是否相等,equals()
区分大小写,equalsIgnoreCase()
忽略大小写。
String str1 = "hello";
String str2 = "HELLO";
System.out.println(str1.equals(str2)); // 输出 false
System.out.println(str1.equalsIgnoreCase(str2)); // 输出 true
(7) indexOf(String str)
和 lastIndexOf(String str)
查找子字符串的位置,indexOf()
返回第一次出现的位置,lastIndexOf()
返回最后一次出现的位置。
String str = "Hello, World!";
System.out.println(str.indexOf("World")); // 输出 7
System.out.println(str.lastIndexOf("l")); // 输出 10
(8) replace()
和 replaceAll()
替换字符串中的字符或子字符串。
String str = "Hello, World!";
System.out.println(str.replace("World", "Java")); // 输出 "Hello, Java!"
(9) split(String regex)
根据正则表达式分割字符串,返回一个字符串数组。
String str = "apple,banana,orange";
String[] fruits = str.split(",");
for (String fruit : fruits) {
System.out.println(fruit);
}
// 输出:
// apple
// banana
// orange
5. 字符串拼接
字符串的拼接有多种方式,但需要注意字符串是不可变的,每次拼接都会创建新的字符串对象。
(1) 使用 +
运算符
String str = "Hello";
str = str + " World"; // 创建了新的字符串对象
(2) 使用 StringBuilder
或 StringBuffer
StringBuilder
和 StringBuffer
是可变的字符序列,适合在循环或频繁拼接字符串时使用。
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb.toString()); // 输出 "Hello World"
StringBuffer
和 StringBuilder
的区别在于,StringBuffer
是线程安全的,而 StringBuilder
不是,通常建议在单线程环境下使用 StringBuilder
,因为它性能更高。
6. 字符串与其他类型的转换
-
将其他类型转换为字符串
int num = 123; String str = String.valueOf(num); // 将数字转换为字符串
-
将字符串转换为其他类型
String str = "123"; int num = Integer.parseInt(str); // 将字符串转换为整数
7. String.format()
String.format()
方法可以用于格式化字符串,类似于 C 语言中的 printf
:
String str = String.format("My name is %s and I am %d years old", "Alice", 25);
System.out.println(str); // 输出 "My name is Alice and I am 25 years old"
8. 字符串的内存管理
(1) 堆内存 vs. 常量池
- 堆内存:通过
new
创建的字符串会分配在堆内存中,并且是独立的对象。 - 常量池:通过字面量创建的字符串(如
"Java"
)会被存储在常量池中。如果池中已经存在相同的字符串,JVM 直接返回该引用,否则创建新的字符串。
(2) 垃圾回收
字符串池中的字符串并不会被垃圾回收器清除,除非显式使用 intern()
方法将其从池中移除。这意味着池中的字符串会在程序运行期间一直存在,而普通的字符串对象则由垃圾回收器回收。
9. 性能优化:StringBuilder 和 StringBuffer
由于字符串的不可变性,频繁的字符串拼接可能会导致性能问题,特别是在循环中。每次拼接都会创建新的字符串对象,从而增加内存负担。
为此,StringBuilder
和 StringBuffer
提供了可变的字符序列,避免了这种不必要的创建新对象的开销。
StringBuilder 示例:
StringBuilder sb = new StringBuilder("Hello");
sb.append(" ");
sb.append("World!");
System.out.println(sb.toString()); // 输出 "Hello World!"
- StringBuilder:非线程安全,适用于单线程环境,效率较高。
- StringBuffer:线程安全,适用于多线程环境,但效率较低。
10. String.format()
String.format()
方法提供了一种简洁的格式化方式,用来构建格式化的字符串,类似于 C 语言中的 printf
。
int age = 25;
String name = "Alice";
String str = String.format("My name is %s and I am %d years old.", name, age);
System.out.println(str); // 输出 "My name is Alice and I am 25 years old."
11. intern()
方法
intern()
方法可以将字符串添加到字符串池中,如果池中已存在该字符串,则返回池中的引用。如果没有,则将该字符串加入池中。
String str1 = new String("Hello");
String str2 = str1.intern(); // 将 str1 加入字符串池
String str3 = "Hello";
System.out.println(str2 == str3); // 输出 true,因为它们指向相同的池内对象
总结
Java 的字符串是不可变的对象,它的设计保证了安全性和高效性,特别是在多线程环境中。字符串常量池和垃圾回收机制有助于优化内存管理。通过 StringBuilder
和 StringBuffer
,可以高效地进行字符串拼接。Java 提供了丰富的字符串操作方法,使得我们能够灵活地进行字符串处理。