1. String类基础概念解析
String是Java中最基础、最常用的类之一,它代表不可变的字符序列。每个用双引号括起来的字符串(如"Hello")都是String类的一个实例。String对象一旦创建,其内容就不能被修改,这种不可变性(immutability)是String类最显著的特性。
1.1 String的不可变性原理
String的不可变性主要体现在三个方面:
- String类被final修饰,不能被继承
- 存储字符数据的char[]数组被final修饰
- 所有修改字符串内容的方法都返回新创建的String对象
这种设计带来几个关键优势:
- 线程安全:无需同步即可在多线程环境下安全使用
- 缓存哈希值:String常用作HashMap的键,不可变性保证了哈希值的一致性
- 字符串常量池优化:JVM可以缓存字符串字面量,减少内存开销
String s1 = "hello"; // 在常量池创建 String s2 = "hello"; // 复用常量池中的对象 String s3 = new String("hello"); // 强制在堆中创建新对象 System.out.println(s1 == s2); // true,指向同一对象 System.out.println(s1 == s3); // false,不同对象1.2 字符串常量池机制
JVM为了优化字符串内存使用,设计了字符串常量池(String Pool),它是方法区的一部分。当创建字符串字面量时,JVM会先检查常量池中是否已存在相同内容的字符串:
- 如果存在,则直接返回池中的引用
- 如果不存在,则在池中创建新对象并返回引用
使用new关键字创建字符串时,会强制在堆中创建新对象,即使内容相同。可以通过intern()方法将堆中的字符串对象放入常量池。
注意:在Java 7之前,字符串常量池位于永久代(PermGen),容易引发OOM。Java 7及以后版本将其移至堆内存,可以通过-XX:StringTableSize参数调整池大小。
2. String类核心API详解
2.1 字符串创建与初始化
String类提供了多种构造方法,最常用的包括:
// 1. 字面量方式(推荐) String s1 = "Java"; // 2. 无参构造 String s2 = new String(); // 创建空字符串"" // 3. 基于字符数组 char[] chars = {'J','a','v','a'}; String s3 = new String(chars); // 4. 基于字节数组(需指定字符集) byte[] bytes = {74, 97, 118, 97}; String s4 = new String(bytes, StandardCharsets.UTF_8); // 5. 基于StringBuilder/StringBuffer StringBuilder sb = new StringBuilder("Java"); String s5 = new String(sb);2.2 字符串比较操作
String类提供了多种比较方法,各有适用场景:
| 方法 | 描述 | 示例 | 返回值 |
|---|---|---|---|
| equals() | 内容比较(区分大小写) | "Java".equals("java") | false |
| equalsIgnoreCase() | 内容比较(不区分大小写) | "Java".equalsIgnoreCase("java") | true |
| compareTo() | 字典序比较 | "a".compareTo("b") | -1 |
| == | 引用地址比较 | new String("a") == "a" | false |
// 比较最佳实践 String input = getUserInput(); if ("quit".equalsIgnoreCase(input)) { // 避免NPE的写法 System.exit(0); }2.3 字符串查找与截取
String类提供了丰富的查找方法:
String text = "Java programming is fun"; // 查找字符/字符串位置 int index1 = text.indexOf('a'); // 1(第一个'a'的位置) int index2 = text.lastIndexOf('a'); // 3(最后一个'a'的位置) int index3 = text.indexOf("pro"); // 5 // 检查包含关系 boolean contains = text.contains("fun"); // true // 提取子字符串 String sub1 = text.substring(5); // "programming is fun" String sub2 = text.substring(5, 16); // "programming" // 获取字符 char ch = text.charAt(0); // 'J'注意:substring()在Java 7u6之前使用共享字符数组,可能导致内存泄漏。新版改为复制数组,确保安全但增加内存开销。
3. 字符串操作进阶技巧
3.1 字符串拼接与格式化
Java提供了多种字符串拼接方式,各有优缺点:
- +运算符:编译时优化为StringBuilder,但循环中使用会创建多个Builder对象
- concat()方法:适合少量拼接,每次调用都创建新String对象
- StringBuilder:可变字符序列,非线程安全,性能最佳
- StringBuffer:线程安全版本,性能稍差
- String.format():支持格式化字符串
// 性能对比示例 long start = System.currentTimeMillis(); String result = ""; for (int i = 0; i < 10000; i++) { result += i; // 低效!每次循环都创建新Builder和String } long end = System.currentTimeMillis(); System.out.println("+操作耗时: " + (end - start) + "ms"); // 使用StringBuilder优化 start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append(i); } result = sb.toString(); end = System.currentTimeMillis(); System.out.println("StringBuilder耗时: " + (end - start) + "ms");格式化字符串示例:
String name = "Alice"; int age = 25; String info = String.format("Name: %s, Age: %d", name, age); // Java 15+ 支持文本块 String json = """ { "name": "%s", "age": %d } """.formatted(name, age);3.2 字符串分割与连接
String类提供了强大的分割与连接功能:
// 分割字符串 String csv = "Java,Python,C++,JavaScript"; String[] languages = csv.split(","); // ["Java", "Python", "C++", "JavaScript"] // 复杂分割(正则表达式) String log = "2023-08-20 14:30:00 [INFO] System started"; String[] parts = log.split("\\s+|\\[|\\]"); // 按空格或方括号分割 // 连接字符串 String joined = String.join(" - ", "Java", "Python", "C++"); // "Java - Python - C++"提示:split()方法使用正则表达式,特殊字符需要转义。对于简单分割,考虑使用StringTokenizer(虽然已过时,但性能更好)。
4. 字符串转换与编码
4.1 基本类型转换
String与基本类型的相互转换:
// String -> 基本类型 int num = Integer.parseInt("123"); double d = Double.parseDouble("3.14"); boolean b = Boolean.parseBoolean("true"); // 基本类型 -> String String s1 = String.valueOf(123); // "123" String s2 = Integer.toString(456); // "456" String s3 = 3.14 + ""; // "3.14"(简便写法)4.2 字符编码处理
正确处理字符编码可以避免乱码问题:
String text = "你好,世界"; // String -> byte[] byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8); byte[] gbkBytes = text.getBytes("GBK"); // byte[] -> String String decoded1 = new String(utf8Bytes, StandardCharsets.UTF_8); String decoded2 = new String(gbkBytes, "GBK"); // 编码检测(第三方库如juniversalchardet更准确) String charset = detectCharset(unknownBytes); // 自定义检测方法常见编码问题:
- 读取文件时未指定正确编码
- HTTP通信时未设置Content-Type charset
- 数据库连接未指定正确字符集
- 跨平台文件传输时的编码不一致
5. 性能优化与最佳实践
5.1 字符串内存优化
- 重用字符串字面量:尽量使用双引号创建字符串,利用常量池
- 适时使用intern():适合大量重复字符串场景,但不要滥用
- 避免大字符串拼接:使用StringBuilder代替+操作
- 考虑使用char[]:对性能敏感且需要频繁修改的场景
// intern使用示例 String s1 = new String("hello").intern(); // 放入常量池 String s2 = "hello"; System.out.println(s1 == s2); // true5.2 正则表达式优化
String的许多方法(如matches()、split())内部使用正则表达式:
// 预编译正则表达式提升性能 Pattern pattern = Pattern.compile("\\d+"); // 匹配数字 Matcher matcher = pattern.matcher("abc123"); boolean found = matcher.find(); // true // 简单判断时避免使用正则 String input = "123"; if (input.matches("\\d+")) { // 每次调用都重新编译正则 // 改用Character.isDigit遍历更高效 }5.3 字符串缓存策略
对于频繁使用的字符串,可以考虑缓存策略:
// 简单缓存实现 class StringCache { private static final Map<String, String> CACHE = new ConcurrentHashMap<>(); public static String getCached(String s) { return CACHE.computeIfAbsent(s, k -> k); } } // 使用缓存 String s1 = StringCache.getCached("hello"); String s2 = StringCache.getCached("hello"); System.out.println(s1 == s2); // true6. Java 8+新特性与字符串
6.1 字符串Join增强
Java 8引入了String.join()静态方法:
List<String> languages = Arrays.asList("Java", "Python", "C++"); String result = String.join(", ", languages); // "Java, Python, C++"6.2 字符串流式处理
结合Stream API实现复杂处理:
String text = "Java,Python,C++,JavaScript"; // 分割、过滤、处理、连接 String result = Arrays.stream(text.split(",")) .filter(s -> !s.startsWith("J")) .map(String::toUpperCase) .collect(Collectors.joining(" | ")); // 输出: "PYTHON | C++"6.3 文本块(Java 15+)
多行字符串的便捷写法:
// 传统方式 String html = "<html>\n" + " <body>\n" + " <p>Hello</p>\n" + " </body>\n" + "</html>"; // 文本块方式 String htmlBlock = """ <html> <body> <p>Hello</p> </body> </html> """;文本块会自动处理缩进和换行,使代码更清晰易读。
7. 常见问题与解决方案
7.1 内存泄漏问题
场景:处理大字符串的子字符串
String bigString = "非常长的字符串..."; String smallPart = bigString.substring(0, 10); // Java 7u6前会共享char[] bigString = null; // 但smallPart仍引用原数组,导致内存无法释放解决方案:
- 升级到Java 7u6+版本
- 手动创建新字符串:
new String(bigString.substring(0, 10))
7.2 编码不一致问题
症状:中文字符显示为乱码 解决方案:
- 明确指定字符集:
new String(bytes, "UTF-8") - 统一项目中的编码设置
- 使用StandardCharsets常量避免拼写错误
7.3 性能热点问题
场景:大量字符串拼接导致性能下降 优化方案:
- 使用StringBuilder代替+操作
- 预估初始容量减少扩容:
new StringBuilder(1024) - 考虑使用字符数组直接操作
7.4 正则表达式灾难回溯
场景:复杂正则导致CPU 100%
// 危险的正则示例(容易发生灾难性回溯) String regex = "(a+)+b"; String input = "aaaaaaaaaaaaaaaaaaaaac"; boolean matches = input.matches(regex); // 可能导致长时间运行解决方案:
- 简化正则表达式
- 使用 possessive quantifier(如++、*+)
- 设置超时限制
8. 实战案例:字符串处理工具类
下面是一个综合性的字符串工具类实现:
public class StringUtils { private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); /** * 检查字符串是否为有效邮箱格式 */ public static boolean isValidEmail(String email) { return email != null && EMAIL_PATTERN.matcher(email).matches(); } /** * 安全截取字符串,避免IndexOutOfBoundsException */ public static String safeSubstring(String str, int start, int end) { if (str == null) return null; start = Math.max(start, 0); end = Math.min(end, str.length()); return start > end ? "" : str.substring(start, end); } /** * 计算字符串相似度(Levenshtein距离算法) */ public static double similarity(String s1, String s2) { if (s1 == null || s2 == null) return 0; if (s1.equals(s2)) return 1; int[][] dp = new int[s1.length()+1][s2.length()+1]; for (int i = 0; i <= s1.length(); i++) dp[i][0] = i; for (int j = 0; j <= s2.length(); j++) dp[0][j] = j; for (int i = 1; i <= s1.length(); i++) { for (int j = 1; j <= s2.length(); j++) { int cost = s1.charAt(i-1) == s2.charAt(j-1) ? 0 : 1; dp[i][j] = Math.min(Math.min( dp[i-1][j] + 1, // 删除 dp[i][j-1] + 1), // 插入 dp[i-1][j-1] + cost // 替换 ); } } int maxLen = Math.max(s1.length(), s2.length()); return 1 - (double)dp[s1.length()][s2.length()] / maxLen; } /** * 移除字符串中的HTML标签 */ public static String stripHtmlTags(String html) { if (html == null) return null; return html.replaceAll("<[^>]+>", ""); } }这个工具类展示了String API的综合应用,包括:
- 正则表达式验证
- 安全字符串操作
- 算法实现
- HTML处理
在实际项目中,可以根据需求进一步扩展功能,如:
- 添加字符串加密/解密方法
- 实现更复杂的文本分析功能
- 支持Markdown等标记语言的转换