第26章:String字符串

掌握Java字符串的核心概念和实用技巧

🎯 学习目标

📖 String字符串概述

String是Java中最常用的类之一,用于表示字符序列。String类具有不可变性(immutable)的特征,这意味着一旦创建了String对象,就不能修改其内容。

🔑 核心特性

  • 不可变性:String对象一旦创建,内容不可修改
  • 字符串池:JVM维护一个字符串常量池来优化内存使用
  • 线程安全:由于不可变性,String天然线程安全
  • 丰富的API:提供大量字符串操作方法

🛠️ 字符串创建方式

1. 字面量创建

// 使用字面量创建(推荐)
String str1 = "Hello World";
String str2 = "Hello World"; // 指向同一个对象

// 验证是否为同一对象
System.out.println(str1 == str2); // true

2. 构造方法创建

// 使用构造方法创建
String str3 = new String("Hello World");
String str4 = new String("Hello World");

// 每次都创建新对象
System.out.println(str3 == str4); // false
System.out.println(str3.equals(str4)); // true

3. 字符数组创建

// 从字符数组创建
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str5 = new String(chars);
System.out.println(str5); // "Hello"

🔧 常用方法详解

📏 长度和判空

String text = "Hello World";

// 获取长度
int length = text.length(); // 11

// 判断是否为空
boolean isEmpty = text.isEmpty(); // false
boolean isBlank = text.isBlank(); // false (Java 11+)

// 安全的判空方法
if (text != null && !text.isEmpty()) {
    System.out.println("字符串不为空");
}

🔍 查找和比较

String text = "Hello World";

// 查找字符或子字符串
int index1 = text.indexOf('o'); // 4
int index2 = text.lastIndexOf('o'); // 7
int index3 = text.indexOf("World"); // 6

// 字符串比较
boolean equals = text.equals("Hello World"); // true
boolean equalsIgnoreCase = text.equalsIgnoreCase("hello world"); // true
int compareResult = text.compareTo("Hello"); // 正数

// 包含判断
boolean contains = text.contains("World"); // true
boolean startsWith = text.startsWith("Hello"); // true
boolean endsWith = text.endsWith("World"); // true

✂️ 截取和替换

String text = "Hello World";

// 字符串截取
String sub1 = text.substring(6); // "World"
String sub2 = text.substring(0, 5); // "Hello"

// 字符串替换
String replaced1 = text.replace('o', 'a'); // "Hella Warld"
String replaced2 = text.replace("World", "Java"); // "Hello Java"
String replaced3 = text.replaceAll("[aeiou]", "*"); // "H*ll* W*rld"

// 去除空白字符
String trimmed = "  Hello World  ".trim(); // "Hello World"
String stripped = "  Hello World  ".strip(); // "Hello World" (Java 11+)

🔄 大小写转换

String text = "Hello World";

// 大小写转换
String upper = text.toUpperCase(); // "HELLO WORLD"
String lower = text.toLowerCase(); // "hello world"

// 首字母大写(自定义方法)
public static String capitalize(String str) {
    if (str == null || str.isEmpty()) {
        return str;
    }
    return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}

📝 分割和连接

// 字符串分割
String csv = "apple,banana,orange";
String[] fruits = csv.split(","); // ["apple", "banana", "orange"]

// 字符串连接
String joined = String.join(", ", fruits); // "apple, banana, orange"

// 使用StringBuilder进行高效连接
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // "Hello World"

🏊 字符串池详解

字符串池(String Pool)是JVM在堆内存中维护的一个特殊区域,用于存储字符串字面量,避免重复创建相同的字符串对象。

字符串池示例

// 字面量会进入字符串池
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true,指向同一个对象

// new创建的对象不在字符串池中
String s3 = new String("Hello");
System.out.println(s1 == s3); // false,不同对象
System.out.println(s1.equals(s3)); // true,内容相同

// intern()方法可以将字符串加入池中
String s4 = s3.intern();
System.out.println(s1 == s4); // true,现在指向池中的对象

⚡ StringBuilder vs StringBuffer

当需要频繁修改字符串时,应该使用StringBuilder或StringBuffer,而不是String。

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 安全 不安全 安全
性能 频繁修改时较慢 较快
使用场景 字符串不变 单线程频繁修改 多线程频繁修改

性能对比示例

// 低效的字符串拼接(避免)
String result = "";
for (int i = 0; i < 1000; i++) {
    result += "Hello "; // 每次都创建新对象
}

// 高效的字符串拼接(推荐)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("Hello "); // 在原对象上修改
}
String result = sb.toString();

💻 在线IDE体验

点击下面的链接,在在线IDE中实践String字符串的各种操作:

💡 在IDE中你可以运行示例代码,观察字符串的创建、操作和性能差异。

💡 最佳实践

✅ 推荐做法

  • 使用字面量创建字符串:优先使用 "Hello" 而不是 new String("Hello")
  • 使用equals()比较内容:避免使用 == 比较字符串内容
  • 频繁修改时使用StringBuilder:避免字符串拼接的性能问题
  • 及时进行空值检查:防止NullPointerException
  • 使用String.format()格式化:提高代码可读性

❌ 避免的做法

  • 避免不必要的String对象创建:减少内存开销
  • 避免在循环中进行字符串拼接:使用StringBuilder代替
  • 避免使用==比较字符串内容:可能导致逻辑错误
  • 避免忽略大小写敏感性:根据需求选择合适的比较方法

最佳实践示例

// ✅ 好的做法
public class StringBestPractices {
    
    // 安全的字符串比较
    public static boolean safeEquals(String str1, String str2) {
        return Objects.equals(str1, str2);
    }
    
    // 高效的字符串构建
    public static String buildMessage(String[] parts) {
        if (parts == null || parts.length == 0) {
            return "";
        }
        
        StringBuilder sb = new StringBuilder();
        for (String part : parts) {
            if (part != null && !part.isEmpty()) {
                sb.append(part).append(" ");
            }
        }
        return sb.toString().trim();
    }
    
    // 格式化字符串
    public static String formatUserInfo(String name, int age) {
        return String.format("用户:%s,年龄:%d岁", name, age);
    }
}

❓ 常见问题

Q: 为什么String是不可变的?

A: String的不可变性带来了多个好处:

  • 线程安全:多个线程可以安全地访问同一个String对象
  • 字符串池优化:相同内容的字符串可以共享同一个对象
  • 哈希码缓存:String的hashCode()只需计算一次
  • 安全性:防止字符串内容被意外修改

Q: 什么时候使用StringBuilder?

A: 在以下情况下应该使用StringBuilder:

  • 需要频繁修改字符串内容
  • 在循环中进行字符串拼接
  • 构建大型字符串
  • 单线程环境下的字符串操作

Q: String.intern()方法的作用是什么?

A: intern()方法的作用:

  • 将字符串对象加入到字符串池中
  • 如果池中已存在相同内容的字符串,返回池中的引用
  • 如果池中不存在,将当前字符串加入池中并返回引用
  • 可以节省内存,但要谨慎使用,避免内存泄漏

⚠️ 注意事项

  • 空值处理:始终检查字符串是否为null,避免NullPointerException
  • 性能考虑:频繁的字符串操作应使用StringBuilder或StringBuffer
  • 内存使用:谨慎使用intern()方法,避免字符串池过大
  • 字符编码:处理文件或网络数据时注意字符编码问题
  • 正则表达式:使用split()和replaceAll()时注意正则表达式的特殊字符