1. Java String类基础解析
String类是Java语言中最基础也最常用的类之一,它代表不可变的字符序列。在Java中,所有用双引号括起来的字符串(如"Hello World")都是String类的实例。
1.1 String类的不可变性
String对象一旦创建,其内容就不能被改变。这种设计带来了几个重要特性:
- 线程安全:由于不可变,String对象可以被多个线程安全共享
- 缓存哈希值:String类缓存了hashCode()的计算结果,因为内容不变,哈希值也不会变
- 字符串池优化:JVM可以使用字符串池来存储字符串字面量,节省内存
String s1 = "hello"; String s2 = s1.concat(" world"); // 创建新对象,s1不变 System.out.println(s1); // 输出"hello" System.out.println(s2); // 输出"hello world"1.2 String的创建方式
Java中创建String对象主要有两种方式:
- 字面量方式:直接使用双引号创建
String s1 = "hello"; - new关键字方式:通过构造函数创建
String s2 = new String("hello");
这两种方式在内存分配上有重要区别:
- 字面量方式会检查字符串池,如果存在则直接引用,否则创建新对象并放入池中
- new方式每次都会在堆中创建新对象,不检查字符串池
2. String类的核心方法详解
2.1 字符串比较方法
String类提供了多种比较方法,各有不同的用途:
equals():比较字符串内容是否相同
String s1 = "hello"; String s2 = "HELLO"; System.out.println(s1.equals(s2)); // falseequalsIgnoreCase():忽略大小写比较内容
System.out.println(s1.equalsIgnoreCase(s2)); // truecompareTo():按字典顺序比较字符串
System.out.println("apple".compareTo("banana")); // 负数==运算符:比较对象引用是否相同
String s3 = "hello"; String s4 = "hello"; String s5 = new String("hello"); System.out.println(s3 == s4); // true System.out.println(s3 == s5); // false
2.2 字符串查找方法
String类提供了丰富的查找方法:
indexOf():查找字符或子串首次出现的位置
String str = "Hello World"; System.out.println(str.indexOf('o')); // 4 System.out.println(str.indexOf("World")); // 6lastIndexOf():查找字符或子串最后一次出现的位置
System.out.println(str.lastIndexOf('o')); // 7contains():检查是否包含指定字符序列
System.out.println(str.contains("llo")); // truestartsWith()/endsWith():检查字符串开头/结尾
System.out.println(str.startsWith("Hello")); // true System.out.println(str.endsWith("ld")); // true
2.3 字符串操作与转换
substring():获取子字符串
System.out.println(str.substring(6)); // "World" System.out.println(str.substring(0, 5)); // "Hello"concat():连接字符串
System.out.println("Hello".concat(" World")); // "Hello World"replace():替换字符或字符串
System.out.println("hello".replace('l', 'L')); // "heLLo"toUpperCase()/toLowerCase():大小写转换
System.out.println(str.toUpperCase()); // "HELLO WORLD"trim():去除首尾空白字符
System.out.println(" hello ".trim()); // "hello"
3. String与正则表达式
String类提供了强大的正则表达式支持:
3.1 matches()方法
检查整个字符串是否匹配正则表达式:
String email = "test@example.com"; System.out.println(email.matches("^[\\w-]+@[\\w-]+\\.[a-z]{2,3}$")); // true3.2 split()方法
根据正则表达式分割字符串:
String data = "apple,orange,banana"; String[] fruits = data.split(","); for (String fruit : fruits) { System.out.println(fruit); }3.3 replaceAll()/replaceFirst()方法
使用正则表达式替换:
String text = "foo123bar456baz"; System.out.println(text.replaceAll("\\d+", "-")); // "foo-bar-baz" System.out.println(text.replaceFirst("\\d+", "-")); // "foo-bar456baz"4. String与字符编码
4.1 编码转换
String类提供了与字节数组相互转换的方法,支持指定字符集:
String str = "你好"; byte[] utf8Bytes = str.getBytes(StandardCharsets.UTF_8); String newStr = new String(utf8Bytes, StandardCharsets.UTF_8);4.2 字符集注意事项
- 默认使用平台字符集,可能导致跨平台问题
- 推荐明确指定字符集,如UTF-8
- 处理IO时,字符集不一致会导致乱码
5. String性能优化
5.1 字符串拼接的优化
使用StringBuilder进行多次拼接:
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100; i++) { sb.append(i).append(", "); } String result = sb.toString();避免在循环中使用+操作符拼接字符串:
// 低效做法 String s = ""; for (int i = 0; i < 100; i++) { s += i; // 每次循环创建新String对象 }
5.2 字符串池的利用
- 尽量使用字面量方式创建字符串
- 使用intern()方法将字符串显式加入字符串池:
String s1 = new String("hello").intern(); String s2 = "hello"; System.out.println(s1 == s2); // true
6. 常见问题与解决方案
6.1 字符串比较问题
问题:使用==比较字符串内容
String s1 = "hello"; String s2 = new String("hello"); System.out.println(s1 == s2); // false解决方案:始终使用equals()方法比较内容
6.2 字符串不可变导致的性能问题
问题:频繁修改字符串产生大量临时对象
String s = ""; for (int i = 0; i < 10000; i++) { s += i; // 性能极差 }解决方案:使用StringBuilder
6.3 内存泄漏问题
问题:超大字符串的子字符串保持原字符串引用
String huge = "...非常长的字符串..."; String small = huge.substring(0, 10); // small仍引用huge的char[]解决方案(Java 7+):使用new String(small)切断引用
6.4 编码转换问题
问题:未指定字符集导致乱码
byte[] bytes = "你好".getBytes(); // 使用平台默认字符集 String s = new String(bytes, "ISO-8859-1"); // 乱码解决方案:始终明确指定字符集
byte[] bytes = "你好".getBytes(StandardCharsets.UTF_8); String s = new String(bytes, StandardCharsets.UTF_8);7. Java 8及以后版本的String增强
7.1 Java 8的StringJoiner
简化字符串拼接:
StringJoiner sj = new StringJoiner(", ", "[", "]"); sj.add("apple").add("orange").add("banana"); System.out.println(sj.toString()); // "[apple, orange, banana]"7.2 Java 11的String新方法
isBlank():检查字符串是否为空或仅包含空白字符
System.out.println(" ".isBlank()); // truelines():将字符串按行分割为流
String text = "line1\nline2\nline3"; text.lines().forEach(System.out::println);repeat():重复字符串
System.out.println("abc".repeat(3)); // "abcabcabc"strip():去除首尾空白字符(比trim()更严格)
System.out.println(" hello ".strip()); // "hello"
8. 实际应用案例
8.1 字符串反转
public static String reverse(String input) { return new StringBuilder(input).reverse().toString(); }8.2 检查回文字符串
public static boolean isPalindrome(String str) { String clean = str.replaceAll("\\s+", "").toLowerCase(); return new StringBuilder(clean).reverse().toString().equals(clean); }8.3 统计字符出现次数
public static int countChar(String str, char c) { return (int) str.chars().filter(ch -> ch == c).count(); }8.4 字符串格式化
String formatted = String.format("Name: %s, Age: %d, Salary: %.2f", "John", 30, 5000.555); System.out.println(formatted); // "Name: John, Age: 30, Salary: 5000.56"9. String与集合框架
9.1 字符串排序
List<String> names = Arrays.asList("John", "Alice", "Bob", "Charlie"); Collections.sort(names); // 自然顺序排序 names.sort(Comparator.comparingInt(String::length)); // 按长度排序9.2 字符串过滤
List<String> list = Arrays.asList("apple", "banana", "cherry", "date"); List<String> filtered = list.stream() .filter(s -> s.startsWith("a")) .collect(Collectors.toList());9.3 字符串分组
List<String> words = Arrays.asList("apple", "banana", "apricot", "berry"); Map<Character, List<String>> grouped = words.stream() .collect(Collectors.groupingBy( s -> s.charAt(0)));10. 高级话题:String与JVM
10.1 字符串池的实现
- JVM维护一个字符串常量池(String Pool)
- 字面量字符串自动入池
- 可以手动调用intern()方法入池
- Java 7+将字符串池从永久代移到堆中
10.2 字符串的内存占用
- 每个String对象包含:
- 对象头(12-16字节,取决于JVM)
- char[]数组引用(4-8字节)
- 哈希值缓存(4字节)
- char[]数组本身占用内存:2字节 × 字符数 + 数组对象头
10.3 字符串压缩
Java 9引入了紧凑字符串(Compact Strings):
- 对于仅含Latin-1字符的字符串,使用byte[]而非char[]存储
- 节省约50%内存
- 完全透明,无需修改代码
11. 最佳实践总结
- 优先使用字符串字面量而非new String()
- 多字符串拼接使用StringBuilder
- 比较字符串内容始终使用equals()而非==
- 明确指定字符集以避免编码问题
- 利用Java新版本提供的字符串方法简化代码
- 注意字符串操作在循环中的性能影响
- 处理用户输入时总是进行空检查和trim()
- 对于大量文本处理,考虑使用更专业的类如StringBuffer(线程安全)或第三方库
- 了解JVM字符串优化特性,如Java 9的紧凑字符串
- 在性能敏感场景,考虑直接操作char[]或使用更高效的数据结构