Java 字符串详解
一、String(字符串常量)
1. 核心特性
-
不可变性:String对象一旦创建,值就不能改变
-
字符串常量池:节省内存,提高性能
-
线程安全:因为不可变
2. 创建方式对比
// 方式1:字面量创建 - 在常量池
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true,指向常量池同一对象
// 方式2:new创建 - 在堆内存
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4); // false,不同对象
System.out.println(s3.equals(s4)); // true,值相同
// 方式3:编译期优化
String s5 = "he" + "llo"; // 编译时优化为"hello"
String s6 = "hello";
System.out.println(s5 == s6); // true
// 方式4:运行时拼接
String s7 = "he";
String s8 = s7 + "llo"; // 运行时创建新对象
String s9 = "hello";
System.out.println(s8 == s9); // false
3. 内存模型分析
String a = "abc"; // 常量池
String b = "abc"; // 常量池,指向同一个
String c = new String("abc"); // 堆内存,新对象
String d = c.intern(); // 放入常量池,返回常量池引用
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a == d); // true
4. 常用方法示例
String str = "Hello,World!Java";
// 1. 长度和判空
int len = str.length(); // 16
boolean empty = str.isEmpty(); // false
boolean blank = str.isBlank(); // false (Java 11+)
// 2. 查找
char ch = str.charAt(1); // 'e'
int index1 = str.indexOf('o'); // 4
int index2 = str.indexOf("Java"); // 12
int index3 = str.lastIndexOf('o'); // 8
boolean contains = str.contains("World"); // true
boolean starts = str.startsWith("Hello"); // true
boolean ends = str.endsWith("Java"); // true
// 3. 截取
String sub1 = str.substring(7); // "World!Java"
String sub2 = str.substring(7, 12); // "World"
// 4. 替换
String rep1 = str.replace('o', '0'); // "Hell0,W0rld!Java"
String rep2 = str.replace("Java", "Python"); // "Hello,World!Python"
String rep3 = str.replaceAll("[aeiou]", "*"); // "H*ll*,W*rld!J*v*"
// 5. 分割
String[] parts = str.split(","); // ["Hello", "World!Java"]
String[] parts2 = str.split("[,!]"); // ["Hello", "World", "Java"]
// 6. 转换
String upper = str.toUpperCase(); // "HELLO,WORLD!JAVA"
String lower = str.toLowerCase(); // "hello,world!java"
String trim = " hello ".trim(); // "hello"
String strip = " hello ".strip(); // "hello" (Java 11+, 支持Unicode)
// 7. 比较
boolean eq1 = "hello".equals("Hello"); // false
boolean eq2 = "hello".equalsIgnoreCase("Hello"); // true
int cmp = "abc".compareTo("abd"); // -1 (字典序比较)
// 8. 其他
String repeat = "ab".repeat(3); // "ababab" (Java 11+)
String join = String.join("-", "A", "B", "C"); // "A-B-C"
String format = String.format("Name: %s, Age: %d", "Tom", 20);
5. 性能陷阱
// ❌ 错误:大量字符串拼接
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都创建新String对象!
}
// 创建了10000个对象,性能极差
// ✅ 正确:使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
==和equals的区别
基本类型用 ==,引用类型用 equals
String 比较用 equals,注意常量池特性
Integer 缓存 -128~127,超出范围 equals
重写 equals 必重写 hashCode,遵守契约
null 比较要小心,用 Objects.equals 最安全
二、StringBuilder(非线程安全可变字符串)
1. 核心特性
-
可变性:内部维护char数组,可修改
-
非线程安全:性能更高
-
单线程首选
2. 常用方法
StringBuilder sb = new StringBuilder();
// 1. 追加
sb.append("Hello");
sb.append(" ").append("World"); // 链式调用
sb.append(123).append(3.14).append(true);
// 2. 插入
sb.insert(5, ","); // 在索引5插入
// 3. 删除
sb.delete(5, 6); // 删除索引5-6(不包括6)
sb.deleteCharAt(0); // 删除第一个字符
// 4. 替换
sb.replace(0, 5, "Hi"); // 替换索引0-5
// 5. 反转
sb.reverse(); // 字符串反转
// 6. 容量相关
sb.ensureCapacity(100); // 确保最小容量
int cap = sb.capacity(); // 当前容量
sb.trimToSize(); // 缩容到实际大小
// 7. 其他
int len = sb.length(); // 实际长度
char ch = sb.charAt(0); // 获取字符
sb.setCharAt(0, 'h'); // 设置字符
String sub = sb.substring(0, 2); // 截取
3. 内部原理
// StringBuilder内部是char数组
public final class StringBuilder extends AbstractStringBuilder {
char[] value; // 存储字符
// 默认构造器:容量16
StringBuilder() {
super(16);
}
// 指定初始容量
StringBuilder(int capacity) {
super(capacity);
}
// 指定初始字符串
StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
}
4. 扩容机制
// 当容量不足时,扩容策略:
// 新容量 = 旧容量 * 2 + 2
// 如果还不够,则直接扩容到需要的大小
StringBuilder sb = new StringBuilder(); // 容量16
for (int i = 0; i < 100; i++) {
sb.append(i); // 会自动扩容
}
// 扩容历史:16 → 34 → 70 → 142
三、StringBuffer(线程安全可变字符串)
1. 核心特性
-
可变性:同StringBuilder
-
线程安全:方法用synchronized修饰
-
性能略低于StringBuilder
2. 与StringBuilder对比
// 性能测试
int count = 1000000;
long start, end;
// StringBuilder测试
start = System.currentTimeMillis();
StringBuilder sb1 = new StringBuilder();
for (int i = 0; i < count; i++) {
sb1.append(i);
}
end = System.currentTimeMillis();
System.out.println("StringBuilder: " + (end - start) + "ms");
// StringBuffer测试
start = System.currentTimeMillis();
StringBuffer sb2 = new StringBuffer();
for (int i = 0; i < count; i++) {
sb2.append(i);
}
end = System.currentTimeMillis();
System.out.println("StringBuffer: " + (end - start) + "ms");
// 结果:StringBuilder 比 StringBuffer 快约20-30%
3. 源码分析
public final class StringBuffer extends AbstractStringBuilder {
// 大部分方法都有 synchronized
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
// 独有的 toStringCache
private transient char[] toStringCache;
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, 0, count);
}
}
四、StringJoiner(Java 8+ 字符串拼接器)
1. 核心特性
-
专门用于拼接字符串序列
-
可指定分隔符、前缀、后缀
-
内部使用StringBuilder
2. 基本使用
// 1. 基本拼接
StringJoiner sj1 = new StringJoiner(", ");
sj1.add("Apple");
sj1.add("Banana");
sj1.add("Orange");
System.out.println(sj1.toString()); // "Apple, Banana, Orange"
// 2. 带前缀后缀
StringJoiner sj2 = new StringJoiner(", ", "[", "]");
sj2.add("Red");
sj2.add("Green");
sj2.add("Blue");
System.out.println(sj2.toString()); // "[Red, Green, Blue]"
// 3. 合并StringJoiner
StringJoiner sj3 = new StringJoiner("-", "(", ")");
sj3.add("A").add("B");
StringJoiner sj4 = new StringJoiner(":", "{", "}");
sj4.add("X").add("Y");
sj3.merge(sj4);
System.out.println(sj3.toString()); // "(A-B-X:Y)"
3. String.join() 的便捷方法
// 数组拼接
String[] arr = {"Java", "Python", "C++"};
String result1 = String.join(" | ", arr); // "Java | Python | C++"
// 集合拼接
List<String> list = Arrays.asList("Tom", "Jerry", "Spike");
String result2 = String.join(", ", list); // "Tom, Jerry, Spike"
// 多个元素拼接
String result3 = String.join("-", "2024", "01", "15"); // "2024-01-15"
五、Pattern 和 Matcher(正则表达式)
1. Pattern(模式)
import java.util.regex.Pattern;
import java.util.regex.Matcher;
// 1. 编译正则表达式
Pattern pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}");
// 2. 创建Matcher
Matcher matcher = pattern.matcher("电话:138-1234-5678");
// 3. 常用方法
boolean matches = matcher.matches(); // 全匹配
boolean find = matcher.find(); // 查找匹配
String found = matcher.group(); // 获取匹配文本
int start = matcher.start(); // 匹配开始索引
int end = matcher.end(); // 匹配结束索引
2. Matcher(匹配器)详解
String text = "订单号:ORD-2024-001, ORD-2024-002, ORD-2024-003";
String regex = "ORD-\\d{4}-\\d{3}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
// 1. 查找所有匹配
System.out.println("查找所有匹配:");
while (matcher.find()) {
System.out.println("找到: " + matcher.group() +
" 位置: " + matcher.start() + "-" + matcher.end());
}
// 2. 重置匹配器
matcher.reset();
// 3. 分组捕获
String html = "<h1>标题</h1><p>内容</p>";
Pattern tagPattern = Pattern.compile("<([a-z][a-z0-9]*)[^>]*>([^<]+)</\\1>");
Matcher tagMatcher = tagPattern.matcher(html);
while (tagMatcher.find()) {
String fullTag = tagMatcher.group(0); // 整个匹配
String tagName = tagMatcher.group(1); // 第一个分组:标签名
String content = tagMatcher.group(2); // 第二个分组:内容
System.out.println("标签: " + tagName + ", 内容: " + content);
}
3. 常用正则表达式示例
// 1. 邮箱验证
Pattern emailPattern = Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
boolean isEmail = emailPattern.matcher("test@example.com").matches();
// 2. 手机号验证
Pattern phonePattern = Pattern.compile("^1[3-9]\\d{9}$");
boolean isPhone = phonePattern.matcher("13812345678").matches();
// 3. 身份证验证
Pattern idPattern = Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$");
// 4. 提取数字
String text2 = "价格:$199.99,重量:2.5kg";
Pattern numPattern = Pattern.compile("\\d+(\\.\\d+)?");
Matcher numMatcher = numPattern.matcher(text2);
while (numMatcher.find()) {
System.out.println("数字: " + numMatcher.group());
}
// 5. 替换操作
String replaced = text2.replaceAll("\\d+", "#");
System.out.println(replaced); // "价格:$#.##,重量:#.#kg"
4. Pattern 的静态方法
// 1. matches() 快速匹配
boolean quickMatch = Pattern.matches("\\d+", "12345"); // true
// 2. split() 分割
String[] parts = Pattern.compile("\\s*,\\s*").split("a,b, c , d");
// ["a", "b", "c", "d"]
// 3. quote() 字面量转义
String literal = Pattern.quote("$100.00");
// 返回 "\Q$100.00\E",可安全用作字面量
六、性能对比与选择指南
1. 性能对比表
| 特性 | String | StringBuilder | StringBuffer | StringJoiner |
|---|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 | 可变(内部用StringBuilder) |
| 线程安全 | 是 | 否 | 是 | 否 |
| 性能 | 低(拼接时) | 高 | 中 | 高 |
| 使用场景 | 常量字符串 | 单线程字符串操作 | 多线程字符串操作 | 拼接带分隔符的序列 |
| 内存 | 常量池/堆 | 堆 | 堆 | 堆 |
2. 选择策略
// 场景1:字符串常量
String str = "hello"; // ✅ 最佳选择
// 场景2:少量字符串拼接
String result = "Hello, " + name + "!"; // ✅ 编译器优化
// 场景3:循环中大量拼接
StringBuilder sb = new StringBuilder(); // ✅ 最佳
for (String item : list) {
sb.append(item);
}
// 场景4:多线程环境拼接
StringBuffer sbf = new StringBuffer(); // ✅ 线程安全
synchronized(lock) {
sbf.append(data);
}
// 场景5:拼接带分隔符的列表
StringJoiner sj = new StringJoiner(","); // ✅ 最简洁
for (String item : list) {
sj.add(item);
}
// 场景6:正则表达式复杂匹配
Pattern pattern = Pattern.compile(regex); // ✅
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
// 处理匹配
}
3. 面试常见问题
Q1:String为什么设计为不可变?
-
安全性:可作为HashMap的key,线程安全
-
缓存哈希值:提高性能
-
字符串常量池:节省内存
-
线程安全:天然线程安全
Q2:String的intern()方法作用?
-
将字符串放入常量池
-
如果常量池已有,返回池中引用
-
如果常量池没有,放入并返回引用
-
用于节省内存
Q3:StringBuilder和StringBuffer区别?
-
线程安全:StringBuffer是线程安全的(synchronized)
-
性能:StringBuilder在单线程下性能更好
-
使用场景:单线程用StringBuilder,多线程用StringBuffer
Q4:如何选择字符串拼接方式?
// 少量固定字符串
String s1 = "a" + "b" + "c"; // 编译器优化
// 循环中拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
// 列表拼接
String.join(", ", list); // Java 8+
StringJoiner sj = new StringJoiner(", ");
// 格式化拼接
String.format("姓名:%s,年龄:%d", name, age);
MessageFormat.format("价格:{0,number,#.##}", price);
Q5:String的长度限制?
-
编译期常量:最大长度65535(类文件限制)
-
运行期:最大Integer.MAX_VALUE(约21亿)
-
实际受JVM堆内存限制
最佳实践总结:
-
字符串常量用
String -
单线程大量拼接用
StringBuilder -
多线程拼接用
StringBuffer -
带分隔符的序列拼接用
StringJoiner -
正则复杂匹配用
Pattern/Matcher -
简单正则用
String的matches()/replaceAll() -
优先使用
String.join()和String.format()等工具方法