Skip to content
清晨的一缕阳光
返回

Java 字符串详解

Java 字符串详解

String 是 Java 中最常用的类,理解其内部机制对性能优化至关重要。

一、String 特性

1.1 不可变性

String s = "Hello";
s = s + " World"; // 创建了新对象

// "Hello" 对象本身没有被修改

为什么不可变

  1. 安全性:字符串常用于参数(数据库 URL、文件路径)
  2. 线程安全:天然线程安全,可共享
  3. 缓存哈希:hashCode 可缓存,提升性能
  4. 字符串池:支持字符串常量池

1.2 源码结构

public final class String implements Serializable, Comparable<String>, CharSequence {
    // JDK 8:char 数组
    private final char value[];
    
    // JDK 9+:byte 数组 + 编码标记
    private final byte[] value;
    private final byte coder; // LATIN1 或 UTF16
    
    // 缓存的哈希值
    private int hash;
}

二、字符串常量池

2.1 什么是常量池

字符串常量池 = JVM 专门存储字符串的区域

相同字符串只保存一份

节省内存,提高效率

2.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(内容相同)

2.3 intern() 方法

String s1 = new String("Hello");
String s2 = s1.intern();
String s3 = "Hello";

System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true(都指向常量池)

// intern() 作用:
// 1. 如果常量池已有,返回池中的引用
// 2. 如果常量池没有,添加到池中并返回

2.4 常量池位置

JDK 6 及之前:永久代(PermGen)
JDK 7:移到堆中
JDK 8+:元空间(Metaspace),但字符串池仍在堆中

三、字符串拼接

3.1 拼接方式对比

// 方式 1:+ 操作符
String s1 = "Hello" + " " + "World";
// 编译优化为 StringBuilder

// 方式 2:StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
String s2 = sb.toString();

// 方式 3:StringBuffer(线程安全)
StringBuffer sbf = new StringBuffer();
sbf.append("Hello").append(" ").append("World");
String s3 = sbf.toString();

// 方式 4:String.join(JDK 8+)
String s4 = String.join(" ", "Hello", "World");

// 方式 5:String.format
String s5 = String.format("%s %s", "Hello", "World");

3.2 性能测试

// 测试代码
int count = 10000;

// + 拼接(循环内)
long start = System.currentTimeMillis();
String s = "";
for (int i = 0; i < count; i++) {
    s += i; // 每次创建新对象
}
System.out.println("+ 拼接:" + (System.currentTimeMillis() - start) + "ms");
// 约 5000ms

// StringBuilder
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
    sb.append(i);
}
String s2 = sb.toString();
System.out.println("StringBuilder: " + (System.currentTimeMillis() - start) + "ms");
// 约 5ms

// 性能差异 1000 倍!

3.3 编译优化

// 源码
String s = "Hello" + " " + "World";

// 编译后
String s = new StringBuilder("Hello")
    .append(" ")
    .append("World")
    .toString();

// 注意:仅在编译期可确定时优化
// 运行期 + 不会优化
String a = "Hello";
String b = a + " World"; // 不会优化为 StringBuilder

四、String vs StringBuilder vs StringBuffer

4.1 核心区别

特性StringStringBuilderStringBuffer
可变性不可变可变可变
线程安全是(synchronized)
性能
适用场景不频繁修改单线程频繁修改多线程频繁修改

4.2 源码对比

// StringBuilder(无锁)
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

// StringBuffer(有锁)
public synchronized StringBuffer append(String str) {
    super.append(str);
    return this;
}

4.3 选择建议

// ✅ 使用 String
String name = "Alice"; // 不修改
String sql = "SELECT * FROM users"; // 常量

// ✅ 使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i); // 单线程频繁修改
}

// ✅ 使用 StringBuffer
StringBuffer sbf = new StringBuffer();
// 多线程共享
new Thread(() -> sbf.append("A")).start();
new Thread(() -> sbf.append("B")).start();

五、常用 API

5.1 查找与匹配

String s = "Hello, World!";

// 查找
s.indexOf("World");      // 7
s.lastIndexOf("o");      // 8
s.contains("World");     // true
s.startsWith("Hello");   // true
s.endsWith("!");         // true

// 匹配
s.matches(".*World.*");  // true
"abc123".matches("\\d+"); // false

5.2 截取与分割

String s = "Hello, World!";

// 截取
s.substring(7);          // "World!"
s.substring(0, 5);       // "Hello"

// 分割
"1,2,3".split(",");      // ["1", "2", "3"]
"a.b.c".split("\\.", 2); // ["a", "b.c"]

// 连接
String.join("-", "a", "b", "c"); // "a-b-c"

5.3 转换

// 大小写
"Hello".toLowerCase();   // "hello"
"hello".toUpperCase();   // "HELLO"

// 去除空格
"  hello  ".trim();      // "hello"
"  hello  ".strip();     // "hello" (JDK 11+)

// 替换
"Hello".replace('l', 'L');    // "HeLLo"
"Hello".replaceAll("l", "L"); // "HeLLo"

// 编码
"你好".getBytes(StandardCharsets.UTF_8);
new String(bytes, StandardCharsets.UTF_8);

六、性能优化

6.1 避免创建过多对象

// ❌ 创建多个对象
String s = "".trim().toUpperCase().trim();

// ✅ 链式调用
String s = original.trim().toUpperCase().trim();

6.2 使用合适的数据结构

// ❌ 字符串拼接
String result = "";
for (String s : list) {
    result += s;
}

// ✅ StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s);
}
String result = sb.toString();

6.3 预分配容量

// ❌ 默认容量(16),可能多次扩容
StringBuilder sb = new StringBuilder();

// ✅ 预估大小
StringBuilder sb = new StringBuilder(1000);

6.4 使用 String.format 替代 +

// ❌ + 拼接(可读性差)
String msg = "User " + name + " is " + age + " years old";

// ✅ String.format(可读性好)
String msg = String.format("User %s is %d years old", name, age);

七、最佳实践

7.1 字符串比较

// ❌ 使用 ==
if (s == "Hello") {}

// ✅ 使用 equals
if ("Hello".equals(s)) {} // 常量在前,避免 NPE

7.2 空字符串判断

// ❌ 冗长
if (s != null && s.length() > 0) {}

// ✅ 简洁
if (s != null && !s.isEmpty()) {}

// ✅ JDK 11+
if (s != null && !s.isBlank()) {} // 包括空白字符

7.3 敏感信息处理

// ❌ String 存储密码(不可变,无法清除)
String password = "secret";

// ✅ char[] 存储密码(可清除)
char[] password = "secret".toCharArray();
// 使用后立即清除
Arrays.fill(password, ' ');

7.4 大字符串处理

// ❌ 内存溢出
String large = new String(new byte[Integer.MAX_VALUE]);

// ✅ 使用流或分块处理
try (BufferedReader reader = Files.newBufferedReader(path)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 逐行处理
    }
}

八、总结

字符串核心要点:

可变性线程安全适用场景
String不可变不频繁修改
StringBuilder可变单线程频繁修改
StringBuffer可变多线程频繁修改

理解字符串常量池和不可变性,选择合适的字符串类,是性能优化的关键。


分享这篇文章到:

上一篇文章
单例模式八种实现
下一篇文章
反射与动态代理详解