9.1 字符串概述
在Java编程中,字符串(String)是使用最频繁的数据类型之一,它代表一个不可变的字符序列。本章将系统性地梳理Java字符串相关的核心类:String、StringBuilder、StringBuffer以及一个较老的字符串分割工具StringTokenizer。理解它们的特性和适用场景,不仅是期末考试的重要考点,更是日后项目开发的实操基础。
学习目标:
- 掌握String的创建、不可变性及其影响。
- 理解StringBuilder和StringBuffer的可变性及适用场景。
- 学会使用StringTokenizer进行字符串分割。
- 能够根据实际需求(如性能、线程安全)选择合适的字符串类。
9.2 String(创建+使用)
String类是java.lang包下的核心类,用于创建和操作不可变的字符串。
9.2.1 创建String对象的两种方式
创建String对象主要有两种方式:直接赋值和使用new关键字。
1. 直接赋值
String s1 = "Hello";
String s2 = "Hello";
运行输出分析:
s1 == s2 的结果为 true。这是因为字符串字面量"Hello"被存储在字符串常量池中。s1和s2指向的是常量池中的同一个对象,所以引用地址相同。
2. 使用new关键字
String s3 = new String("Hello");
String s4 = new String("Hello");
运行输出分析:
s3 == s4 的结果为 false。new关键字会在堆内存中创建一个新的String对象,即使内容相同,s3和s4也是两个不同的对象,引用地址不同。
s3.equals(s4) 的结果为 true,因为equals方法比较的是字符串的内容。
9.2.2 String类的常用方法
String提供了丰富的方法来操作字符串,以下是一些核心方法及其测试代码:
public class StringDemo {
public static void main(String[] args) {
String str = " Java Programming ";
// 1. 获取长度
System.out.println("长度: " + str.length()); // 输出: 长度: 20
// 2. 去除首尾空格
String trimmed = str.trim();
System.out.println("去除空格后: \\"" + trimmed + "\\""); // 输出: 去除空格后: "Java Programming"
// 3. 获取子字符串
String sub = str.substring(5, 16); // 索引从0开始,[5,16)
System.out.println("子串: " + sub); // 输出: 子串: Programming
// 4. 查找字符/字符串位置
int index = str.indexOf("Pro");
System.out.println("\\"Pro\\"的索引: " + index); // 输出: "Pro"的索引: 7
// 5. 字符串替换
String replaced = str.replace("Java", "Python");
System.out.println("替换后: " + replaced); // 输出: 替换后: Python Programming
// 6. 大小写转换
System.out.println("大写: " + str.toUpperCase()); // 输出: 大写: JAVA PROGRAMMING
System.out.println("小写: " + str.toLowerCase()); // 输出: 小写: java programming
// 7. 分割字符串
String csv = "Apple,Banana,Orange";
String[] fruits = csv.split(",");
System.out.println("分割后第一个水果: " + fruits[0]); // 输出: 分割后第一个水果: Apple
}
}
考点提示:牢记substring是前闭后开区间 [beginIndex, endIndex),indexOf找不到返回-1。
9.3 StringBuilder(创建+使用)
StringBuilder代表一个可变的字符序列。当需要频繁修改字符串内容(如拼接、插入、删除)时,使用StringBuilder比String性能高得多,因为它避免了创建大量临时String对象。
9.3.1 创建与基本使用
public class StringBuilderDemo {
public static void main(String[] args) {
// 1. 创建
StringBuilder sb1 = new StringBuilder(); // 默认容量16
StringBuilder sb2 = new StringBuilder("Hello"); // 指定初始内容
StringBuilder sb3 = new StringBuilder(50); // 指定初始容量
// 2. 追加内容
sb1.append("Java");
sb1.append(" ");
sb1.append(17); // 可以追加任何类型,自动转为字符串
System.out.println("追加后: " + sb1.toString()); // 输出: 追加后: Java 17
// 3. 插入内容
sb2.insert(5, " World");
System.out.println("插入后: " + sb2); // 输出: 插入后: Hello World
// 4. 删除内容
sb2.delete(5, 11); // 删除[5,11)位置的字符
System.out.println("删除后: " + sb2); // 输出: 删除后: Hello
// 5. 替换内容
sb2.replace(0, 5, "Hi");
System.out.println("替换后: " + sb2); // 输出: 替换后: Hi
// 6. 反转字符串
sb2.reverse();
System.out.println("反转后: " + sb2); // 输出: 反转后: iH
}
}
实操重点:在单线程环境下进行大量字符串修改操作,StringBuilder是首选。
9.4 StringBuffer
StringBuffer与StringBuilder在功能上几乎完全相同,都代表可变的字符序列。它们的关键区别在于线程安全性。
9.4.1 核心特性与使用
StringBuffer的方法是同步的(synchronized),因此是线程安全的。这意味着多个线程可以安全地同时操作同一个StringBuffer对象。而StringBuilder的方法是非同步的,线程不安全,但在单线程环境下性能更高。
public class StringBufferDemo {
public static void main(String[] args) {
StringBuffer sbf = new StringBuffer("ThreadSafe");
// 方法与StringBuilder类似
sbf.append(" Buffer");
System.out.println(sbf); // 输出: ThreadSafe Buffer
sbf.insert(10, "-");
System.out.println(sbf); // 输出: ThreadSafe- Buffer
}
}
考点与选择策略:
- 考点:StringBuffer vs StringBuilder 的核心区别是线程安全性。
- 实操:绝大多数现代应用都是单线程操作字符串,因此优先使用StringBuilder。只有在明确涉及多线程共享并修改同一个字符串对象时,才考虑使用StringBuffer。
9.5 StringTokenizer
StringTokenizer是一个用于分割字符串的遗留类(Legacy Class),位于java.util包。虽然现在更推荐使用String的split()方法或正则表达式,但理解它仍有必要,尤其是在处理一些简单分隔符或考试中。
9.5.1 基本用法
import java.util.StringTokenizer;
public class StringTokenizerDemo {
public static void main(String[] args) {
String data = "Java,Python,JavaScript,C++";
// 默认分隔符为空格、制表符、换行符等
// 此处指定逗号为分隔符
StringTokenizer st = new StringTokenizer(data, ",");
System.out.println("令牌数量: " + st.countTokens()); // 输出: 令牌数量: 4
// 遍历所有令牌
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// 输出:
// Java
// Python
// JavaScript
// C++
}
}
9.5.2 StringTokenizer vs String.split()
| 所属包 | java.util (遗留类) | java.lang.String |
| 功能 | 仅按分隔符拆分 | 按正则表达式拆分,功能强大 |
| 空令牌处理 | 默认忽略空字符串 | 默认保留空字符串(除非使用限定符) |
| 性能 | 对于简单分隔符,性能稍好 | 正则表达式编译有开销,但功能更灵活 |
| 推荐度 | 不推荐在新代码中使用 | 推荐使用,功能全面,API现代 |
实操建议:除非在处理性能极其敏感且分隔规则极其简单的场景,否则一律使用String.split()或java.util.regex.Pattern。
对比与总结
对比表格1:String直接赋值 vs new String()
| 内存区域 | 字符串常量池 | 堆内存 |
| 对象数量 | 常量池中只有一个对象,所有相同字面量共享 | 每次new都会在堆中创建一个新对象 |
| ==比较 | 相同字面量比较为true | 即使内容相同,比较也为false |
| 适用场景 | 推荐,节省内存 | 需要明确创建独立对象时 |
对比表格2:三大字符串类核心差异
| 可变性 | 不可变 (Immutable) | 可变 (Mutable) | 可变 (Mutable) |
| 线程安全 | 是(因为不可变) | 否 | 是 (方法同步) |
| 性能 | 修改性能差(创建新对象) | 高 (单线程) | 较低(因同步开销) |
| 使用场景 | 存储不变字符串、作为键 | 单线程下频繁修改字符串 | 多线程下共享修改字符串 |
| 诞生版本 | Java 1.0 | Java 5 | Java 1.0 |
易错考点整理
1. 常量池陷阱
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
String s4 = s3.intern(); // 将s3指向的对象放入常量池,并返回常量池引用
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s4); // true (intern后)
考点:理解==比较引用,equals比较内容。intern()方法可以主动将字符串对象放入常量池。
2. 循环拼接性能问题
错误示范(性能极差):
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都创建新的String对象和StringBuilder对象!
}
正确做法(使用StringBuilder):
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
3. 线程安全问题
- String:线程安全(不可变)。
- StringBuilder:非线程安全。多线程操作同一对象需自行加锁。
- StringBuffer:线程安全(内部同步),但性能有损耗。
复习思考题
String s2 = new String("abc");
String s3 = s2.intern();
本文涵盖了Java字符串章节的核心知识点、代码示例与易错点,适合作为期末复习笔记。建议读者亲手运行所有示例代码,并结合思考题进行巩固,祝大家考试顺利!



