Java String 详细教程
文章目录
- 1. String基础概念
- 1.1 什么是String?
- 1.2 String的特点
- 2. String的创建方式
- 2.1 字面量方式创建
- 2.2 new关键字创建
- 2.3 字符数组创建
- 2.4 StringBuilder/StringBuffer转换
- 3. String的不可变性
- 3.1 不可变性的含义
- 3.2 不可变性的原因
- 3.3 不可变性的好处
- 4. String常用方法详解
- 4.1 长度和字符访问
- 4.2 子字符串操作
- 4.3 查找和索引
- 4.4 字符串比较
- 4.5 字符串转换
- 4.6 字符串分割和连接
- 5. String的比较
- 5.1 == 操作符 vs equals方法
- 5.2 intern()方法
- 6. String与StringBuilder/StringBuffer
- 6.1 为什么需要StringBuilder和StringBuffer
- 6.2 StringBuilder详解
- 6.3 StringBuffer详解
- 6.4 三者的选择原则
- 7. String的内存管理
- 7.1 字符串池(String Pool)详解
- 7.2 内存布局分析
- 8. String的性能优化
- 8.1 字符串连接优化
- 9. 常见陷阱和注意事项
- 9.1 空指针异常陷阱
- 9.2 字符编码陷阱
- 9.3 性能陷阱
- 10. 实际应用示例
- 10.1 文本处理工具类
- 10.2 CSV文件处理示例
- 10.3 模板引擎示例
- 总结
- 核心要点回顾:
- 最佳实践:
1. String基础概念
1.1 什么是String?
String(字符串)是Java中最常用的类之一,用于表示字符序列。在Java中,String是一个类,不是基本数据类型。
public class StringBasic {public static void main(String[] args) {// String是引用类型,不是基本类型String str = "Hello World";System.out.println(str);// String类的完整包名System.out.println(str.getClass().getName()); // java.lang.String}
}
1.2 String的特点
- 不可变性(Immutable):一旦创建,内容不能被修改
- 线程安全:由于不可变性,天然线程安全
- 字符串池:相同内容的字符串共享内存空间
- 引用类型:String是对象,不是基本数据类型
public class StringFeatures {public static void main(String[] args) {// 不可变性示例String original = "Hello";String modified = original.concat(" World");System.out.println("原始字符串: " + original); // HelloSystem.out.println("修改后字符串: " + modified); // Hello World// original没有被改变,说明String是不可变的}
}
2. String的创建方式
2.1 字面量方式创建
public class StringCreation1 {public static void main(String[] args) {// 直接赋值(推荐方式)String str1 = "Hello";String str2 = "Hello";// 这两个引用指向同一个对象(字符串池中的对象)System.out.println(str1 == str2); // trueSystem.out.println(str1.equals(str2)); // true}
}
2.2 new关键字创建
public class StringCreation2 {public static void main(String[] args) {// 使用new关键字创建String str1 = new String("Hello");String str2 = new String("Hello");String str3 = "Hello";// new方式创建的对象在堆内存中,不在字符串池中System.out.println(str1 == str2); // falseSystem.out.println(str1 == str3); // falseSystem.out.println(str1.equals(str2)); // trueSystem.out.println(str1.equals(str3)); // true}
}
2.3 字符数组创建
public class StringCreation3 {public static void main(String[] args) {// 从字符数组创建char[] charArray = {'H', 'e', 'l', 'l', 'o'};String str1 = new String(charArray);System.out.println(str1); // Hello// 从字符数组的部分内容创建String str2 = new String(charArray, 1, 3); // 从索引1开始,长度为3System.out.println(str2); // ell// 从字节数组创建byte[] byteArray = {72, 101, 108, 108, 111}; // Hello的ASCII码String str3 = new String(byteArray);System.out.println(str3); // Hello}
}
2.4 StringBuilder/StringBuffer转换
public class StringCreation4 {public static void main(String[] args) {// 从StringBuilder创建StringBuilder sb = new StringBuilder("Hello");sb.append(" World");String str1 = sb.toString();System.out.println(str1); // Hello World// 从StringBuffer创建StringBuffer buffer = new StringBuffer("Java");buffer.append(" Programming");String str2 = buffer.toString();System.out.println(str2); // Java Programming}
}
3. String的不可变性
3.1 不可变性的含义
String对象一旦创建,其内容就不能被修改。任何看似"修改"String的操作实际上都是创建新的String对象。
public class StringImmutable {public static void main(String[] args) {String str = "Java";System.out.println("原始字符串: " + str);System.out.println("原始对象地址: " + System.identityHashCode(str));// 尝试"修改"字符串str = str + " Programming";System.out.println("修改后字符串: " + str);System.out.println("修改后对象地址: " + System.identityHashCode(str));// 地址不同,说明创建了新对象}
}
3.2 不可变性的原因
// String类的部分源码结构(简化版)
public final class String {private final char[] value; // 字符数组是final的private int hash; // 缓存的hash值// 没有提供修改value数组的方法// 所有方法都不会改变原有对象的状态
}
3.3 不可变性的好处
public class ImmutableBenefits {public static void main(String[] args) {// 1. 线程安全String sharedString = "共享字符串";// 多个线程可以安全地读取同一个String对象// 2. 可以作为HashMap的keyMap<String, Integer> map = new HashMap<>();String key = "myKey";map.put(key, 100);// key的hash值不会变化,保证HashMap的正确性// 3. 字符串池优化String s1 = "Hello";String s2 = "Hello";// s1和s2指向同一个对象,节省内存System.out.println(s1 == s2); // true// 4. 安全性String password = "secret123";// 传递给其他方法时,不用担心被恶意修改processPassword(password);System.out.println("密码仍然安全: " + password);}public static void processPassword(String pwd) {// 即使这里尝试修改,也不会影响原始字符串pwd = pwd + "hacked";}
}
4. String常用方法详解
4.1 长度和字符访问
public class StringLengthAndAccess {public static void main(String[] args) {String str = "Hello World";// 获取字符串长度System.out.println("字符串长度: " + str.length()); // 11// 获取指定位置的字符System.out.println("索引0的字符: " + str.charAt(0)); // HSystem.out.println("索引6的字符: " + str.charAt(6)); // W// 获取字符数组char[] charArray = str.toCharArray();System.out.println("字符数组: " + Arrays.toString(charArray));// 判断字符串是否为空String emptyStr = "";String nullStr = null;System.out.println("空字符串长度: " + emptyStr.length()); // 0System.out.println("空字符串isEmpty: " + emptyStr.isEmpty()); // true// System.out.println(nullStr.length()); // 会抛出NullPointerException// 安全检查空字符串的方法if (nullStr != null && !nullStr.isEmpty()) {System.out.println("字符串不为空");}}
}
4.2 子字符串操作
public class StringSubstring {public static void main(String[] args) {String str = "Hello World Java Programming";// substring(beginIndex) - 从指定位置到字符串末尾String sub1 = str.substring(6);System.out.println("从索引6开始: " + sub1); // World Java Programming// substring(beginIndex, endIndex) - 从beginIndex到endIndex-1String sub2 = str.substring(6, 11);System.out.println("从索引6到10: " + sub2); // World// 获取文件扩展名的示例String fileName = "document.pdf";int dotIndex = fileName.lastIndexOf(".");if (dotIndex != -1) {String extension = fileName.substring(dotIndex + 1);System.out.println("文件扩展名: " + extension); // pdf}// 获取文件名(不含扩展名)String nameWithoutExt = fileName.substring(0, dotIndex);System.out.println("文件名: " + nameWithoutExt); // document}
}
4.3 查找和索引
public class StringIndexOf {public static void main(String[] args) {String str = "Hello World Hello Java";// indexOf - 查找字符或字符串第一次出现的位置System.out.println("字符'o'第一次出现位置: " + str.indexOf('o')); // 4System.out.println("字符串'World'第一次出现位置: " + str.indexOf("World")); // 6System.out.println("字符串'Python'的位置: " + str.indexOf("Python")); // -1 (未找到)// indexOf从指定位置开始查找System.out.println("从索引5开始查找'o': " + str.indexOf('o', 5)); // 7// lastIndexOf - 查找最后一次出现的位置System.out.println("字符'o'最后一次出现位置: " + str.lastIndexOf('o')); // 14System.out.println("字符串'Hello'最后一次出现位置: " + str.lastIndexOf("Hello")); // 12// contains - 判断是否包含指定字符串System.out.println("是否包含'World': " + str.contains("World")); // trueSystem.out.println("是否包含'Python': " + str.contains("Python")); // false// startsWith 和 endsWithSystem.out.println("是否以'Hello'开始: " + str.startsWith("Hello")); // trueSystem.out.println("是否以'Java'结束: " + str.endsWith("Java")); // true// 实际应用:检查邮箱格式String email = "user@example.com";if (email.contains("@") && email.endsWith(".com")) {System.out.println("可能是有效的邮箱地址");}}
}
4.4 字符串比较
public class StringComparison {public static void main(String[] args) {String str1 = "Hello";String str2 = "hello";String str3 = "Hello";String str4 = new String("Hello");// equals - 比较内容是否相同(区分大小写)System.out.println("str1.equals(str2): " + str1.equals(str2)); // falseSystem.out.println("str1.equals(str3): " + str1.equals(str3)); // trueSystem.out.println("str1.equals(str4): " + str1.equals(str4)); // true// equalsIgnoreCase - 忽略大小写比较System.out.println("str1.equalsIgnoreCase(str2): " + str1.equalsIgnoreCase(str2)); // true// compareTo - 字典序比较System.out.println("str1.compareTo(str2): " + str1.compareTo(str2)); // 负数System.out.println("str1.compareTo(str3): " + str1.compareTo(str3)); // 0String[] words = {"banana", "apple", "cherry", "date"};Arrays.sort(words);System.out.println("排序后: " + Arrays.toString(words));// compareToIgnoreCase - 忽略大小写的字典序比较String word1 = "Apple";String word2 = "banana";System.out.println("忽略大小写比较: " + word1.compareToIgnoreCase(word2));// 注意:永远不要用 == 比较字符串内容System.out.println("str1 == str3: " + (str1 == str3)); // true (字符串池)System.out.println("str1 == str4: " + (str1 == str4)); // false (不同对象)}
}
4.5 字符串转换
public class StringConversion {public static void main(String[] args) {String str = " Hello World ";// 大小写转换System.out.println("原始字符串: '" + str + "'");System.out.println("转大写: '" + str.toUpperCase() + "'");System.out.println("转小写: '" + str.toLowerCase() + "'");// 去除空格System.out.println("去除首尾空格: '" + str.trim() + "'");// Java 11+ 提供的strip方法(更强大的trim)String strWithUnicodeSpaces = "\u2000\u2001Hello\u2002\u2003";System.out.println("strip处理Unicode空格: '" + strWithUnicodeSpaces.strip() + "'");// 替换操作String original = "Hello World Hello";System.out.println("替换第一个Hello: " + original.replaceFirst("Hello", "Hi"));System.out.println("替换所有Hello: " + original.replaceAll("Hello", "Hi"));System.out.println("替换字符: " + original.replace('l', 'L'));// 数字和字符串转换int number = 123;String numStr = String.valueOf(number);System.out.println("数字转字符串: " + numStr);String numberString = "456";int num = Integer.parseInt(numberString);System.out.println("字符串转数字: " + num);// 格式化字符串String formatted = String.format("姓名: %s, 年龄: %d, 分数: %.2f", "张三", 25, 88.5);System.out.println("格式化字符串: " + formatted);}
}
4.6 字符串分割和连接
public class StringSplitJoin {public static void main(String[] args) {// 字符串分割String csvData = "张三,25,工程师,北京";String[] parts = csvData.split(",");System.out.println("分割结果: " + Arrays.toString(parts));// 按正则表达式分割String text = "apple123banana456orange";String[] fruits = text.split("\\d+"); // 按数字分割System.out.println("按数字分割: " + Arrays.toString(fruits));// 限制分割次数String data = "a,b,c,d,e";String[] limitedSplit = data.split(",", 3); // 最多分割成3部分System.out.println("限制分割: " + Arrays.toString(limitedSplit));// 字符串连接String[] words = {"Hello", "World", "Java"};// 使用join方法(Java 8+)String joined = String.join(" ", words);System.out.println("用空格连接: " + joined);String csvJoined = String.join(",", words);System.out.println("用逗号连接: " + csvJoined);// 处理集合List<String> list = Arrays.asList("A", "B", "C");String listJoined = String.join("-", list);System.out.println("集合连接: " + listJoined);// 手动连接(不推荐,效率低)String manual = "";for (String word : words) {manual += word + " ";}System.out.println("手动连接: " + manual.trim());}
}
5. String的比较
5.1 == 操作符 vs equals方法
public class StringComparisonDetailed {public static void main(String[] args) {// 字符串池中的字符串String s1 = "Hello";String s2 = "Hello";String s3 = "Hel" + "lo"; // 编译时优化// 堆中的新对象String s4 = new String("Hello");String s5 = new String("Hello");// 运行时连接String part = "Hel";String s6 = part + "lo";System.out.println("=== == 操作符比较(比较引用地址)===");System.out.println("s1 == s2: " + (s1 == s2)); // trueSystem.out.println("s1 == s3: " + (s1 == s3)); // trueSystem.out.println("s1 == s4: " + (s1 == s4)); // falseSystem.out.println("s4 == s5: " + (s4 == s5)); // falseSystem.out.println("s1 == s6: " + (s1 == s6)); // falseSystem.out.println("\n=== equals方法比较(比较内容)===");System.out.println("s1.equals(s2): " + s1.equals(s2)); // trueSystem.out.println("s1.equals(s4): " + s1.equals(s4)); // trueSystem.out.println("s4.equals(s5): " + s4.equals(s5)); // trueSystem.out.println("s1.equals(s6): " + s1.equals(s6)); // true// null安全的比较String nullStr = null;String normalStr = "Hello";// 危险的做法// System.out.println(nullStr.equals(normalStr)); // NullPointerException// 安全的做法System.out.println("\n=== 空值安全比较 ===");System.out.println("Objects.equals: " + Objects.equals(nullStr, normalStr)); // falseSystem.out.println("常量在前: " + "Hello".equals(nullStr)); // false// 使用Optional进行安全比较Optional<String> optStr = Optional.ofNullable(nullStr);boolean isEqual = optStr.map(s -> s.equals("Hello")).orElse(false);System.out.println("Optional比较: " + isEqual);}
}
5.2 intern()方法
public class StringIntern {public static void main(String[] args) {// intern()方法将字符串加入字符串池String s1 = new String("Hello");String s2 = s1.intern(); // 返回字符串池中的引用String s3 = "Hello"; // 字符串池中的引用System.out.println("s1 == s2: " + (s1 == s2)); // falseSystem.out.println("s2 == s3: " + (s2 == s3)); // trueSystem.out.println("s1 == s3: " + (s1 == s3)); // false// intern的实际应用场景Set<String> processedStrings = new HashSet<>();// 模拟处理大量重复字符串的场景for (int i = 0; i < 1000; i++) {String str = new String("重复的字符串" + (i % 10));// 使用intern可以减少内存占用processedStrings.add(str.intern());}System.out.println("处理后的唯一字符串数量: " + processedStrings.size());}
}
6. String与StringBuilder/StringBuffer
6.1 为什么需要StringBuilder和StringBuffer
由于String的不可变性,频繁的字符串操作会创建大量临时对象,影响性能。
public class StringConcatenationProblem {public static void main(String[] args) {// 低效的字符串拼接String result = "";long startTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {result += "a"; // 每次都创建新的String对象}long endTime = System.currentTimeMillis();System.out.println("String拼接耗时: " + (endTime - startTime) + "ms");// 高效的字符串拼接StringBuilder sb = new StringBuilder();startTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {sb.append("a"); // 内部数组扩容,不创建新对象}String sbResult = sb.toString();endTime = System.currentTimeMillis();System.out.println("StringBuilder拼接耗时: " + (endTime - startTime) + "ms");}
}
6.2 StringBuilder详解
StringBuilder是可变的字符序列,线程不安全但性能高。
public class StringBuilderDemo {public static void main(String[] args) {// 创建StringBuilderStringBuilder sb1 = new StringBuilder(); // 默认容量16StringBuilder sb2 = new StringBuilder(50); // 指定初始容量StringBuilder sb3 = new StringBuilder("Hello"); // 从字符串创建// 常用方法StringBuilder sb = new StringBuilder("Java");// append - 追加内容sb.append(" Programming");sb.append(" ").append("Language");sb.append(2023);System.out.println("追加后: " + sb); // Java Programming Language2023// insert - 插入内容sb.insert(4, " Core");System.out.println("插入后: " + sb); // Java Core Programming Language2023// delete - 删除内容sb.delete(5, 10); // 删除索引5到9的字符System.out.println("删除后: " + sb); // Java Programming Language2023// replace - 替换内容sb.replace(0, 4, "Python");System.out.println("替换后: " + sb); // Python Programming Language2023// reverse - 反转StringBuilder reversed = new StringBuilder("Hello").reverse();System.out.println("反转: " + reversed); // olleH// 容量相关方法System.out.println("长度: " + sb.length());System.out.println("容量: " + sb.capacity());// 设置长度sb.setLength(6);System.out.println("设置长度后: " + sb); // Python}
}
6.3 StringBuffer详解
StringBuffer功能与StringBuilder相同,但所有方法都是同步的,线程安全。
public class StringBufferDemo {public static void main(String[] args) {StringBuffer buffer = new StringBuffer("Thread Safe");// 在多线程环境中使用Thread t1 = new Thread(() -> {for (int i = 0; i < 100; i++) {buffer.append(" A");}});Thread t2 = new Thread(() -> {for (int i = 0; i < 100; i++) {buffer.append(" B");}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终长度: " + buffer.length());// StringBuffer保证线程安全,不会出现数据竞争}
}
6.4 三者的选择原则
public class StringComparisonGuide {public static void main(String[] args) {// 1. 字符串内容不变或很少改变 -> 使用StringString constant = "不变的字符串";// 2. 单线程环境下频繁修改字符串 -> 使用StringBuilderStringBuilder sb = new StringBuilder();for (int i = 0; i < 1000; i++) {sb.append("data").append(i);}// 3. 多线程环境下频繁修改字符串 -> 使用StringBufferStringBuffer buffer = new StringBuffer();// 在多线程环境中安全使用// 性能对比示例demonstratePerformance();}public static void demonstratePerformance() {int iterations = 10000;// String拼接测试long start = System.nanoTime();String str = "";for (int i = 0; i < iterations; i++) {str += "a";}long stringTime = System.nanoTime() - start;// StringBuilder测试start = System.nanoTime();StringBuilder sb = new StringBuilder();for (int i = 0; i < iterations; i++) {sb.append("a");}String sbResult = sb.toString();long sbTime = System.nanoTime() - start;// StringBuffer测试start = System.nanoTime();StringBuffer buffer = new StringBuffer();for (int i = 0; i < iterations; i++) {buffer.append("a");}String bufferResult = buffer.toString();long bufferTime = System.nanoTime() - start;System.out.printf("String拼接: %d纳秒%n", stringTime);System.out.printf("StringBuilder: %d纳秒%n", sbTime);System.out.printf("StringBuffer: %d纳秒%n", bufferTime);System.out.printf("StringBuilder比String快: %.2f倍%n", (double)stringTime / sbTime);}
}
7. String的内存管理
7.1 字符串池(String Pool)详解
public class StringPoolDemo {public static void main(String[] args) {// 字符串池位于堆内存中(Java 7+)String s1 = "Hello"; // 创建在字符串池中String s2 = "Hello"; // 引用池中已有对象String s3 = new String("Hello"); // 在堆中创建新对象System.out.println("s1 == s2: " + (s1 == s2)); // trueSystem.out.println("s1 == s3: " + (s1 == s3)); // false// 编译时常量折叠String s4 = "Hel" + "lo"; // 编译时合并为"Hello"System.out.println("s1 == s4: " + (s1 == s4)); // true// 运行时字符串连接String part = "Hel";String s5 = part + "lo"; // 运行时连接,创建新对象System.out.println("s1 == s5: " + (s1 == s5)); // false// 使用intern()将字符串加入池中String s6 = s5.intern();System.out.println("s1 == s6: " + (s1 == s6)); // true// 查看对象地址System.out.println("s1地址: " + System.identityHashCode(s1));System.out.println("s2地址: " + System.identityHashCode(s2));System.out.println("s3地址: " + System.identityHashCode(s3));System.out.println("s6地址: " + System.identityHashCode(s6));}
}
7.2 内存布局分析
public class StringMemoryLayout {public static void main(String[] args) {// 1. 字面量 - 存储在字符串池中String literal1 = "Java";String literal2 = "Java";// 2. new创建 - 存储在堆内存中String heap1 = new String("Java");String heap2 = new String("Java");// 3. 字符数组创建char[] charArray = {'J', 'a', 'v', 'a'};String fromArray = new String(charArray);System.out.println("=== 内存地址比较 ===");System.out.println("literal1 == literal2: " + (literal1 == literal2));System.out.println("heap1 == heap2: " + (heap1 == heap2));System.out.println("literal1 == heap1: " + (literal1 == heap1));// 使用intern()观察内存变化String internStr = heap1.intern();System.out.println("literal1 == heap1.intern(): " + (literal1 == internStr));// 内存使用分析analyzeMemoryUsage();}public static void analyzeMemoryUsage() {Runtime runtime = Runtime.getRuntime();long before = runtime.totalMemory() - runtime.freeMemory();// 创建大量重复字符串(不使用intern)String[] strings1 = new String[10000];for (int i = 0; i < strings1.length; i++) {strings1[i] = new String("重复字符串" + (i % 100));}long after1 = runtime.totalMemory() - runtime.freeMemory();System.out.println("不使用intern的内存增长: " + (after1 - before) + " bytes");// 创建大量重复字符串(使用intern)String[] strings2 = new String[10000];for (int i = 0; i < strings2.length; i++) {strings2[i] = new String("重复字符串" + (i % 100)).intern();}long after2 = runtime.totalMemory() - runtime.freeMemory();System.out.println("使用intern后的总内存增长: " + (after2 - before) + " bytes");}
}
8. String的性能优化
8.1 字符串连接优化
public class StringOptimization {public static void main(String[] args) {// 1. 避免循环中的字符串连接demonstrateStringConcatenation();// 2. 使用String.join()进行批量连接demonstrateStringJoin();// 3. 预估StringBuilder容量demonstrateStringBuilderCapacity();// 4. 缓存经常使用的字符串demonstrateStringCaching();}// 字符串连接性能对比public static void demonstrateStringConcatenation() {int count = 10000;// 方法1:直接用+连接(最慢)long start = System.nanoTime();String result1 = "";for (int i = 0; i < count; i++) {result1 += "item" + i + ";";}long time1 = System.nanoTime() - start;// 方法2:使用StringBuilder(推荐)start = System.nanoTime();StringBuilder sb = new StringBuilder();for (int i = 0; i < count; i++) {sb.append("item").append(i).append(";");}String result2 = sb.toString();long time2 = System.nanoTime() - start;// 方法3:预设容量的StringBuilder(最优)start = System.nanoTime();StringBuilder sbWithCapacity = new StringBuilder(count * 10);for (int i = 0; i < count; i++) {sbWithCapacity.append("item").append(i).append(";");}String result3 = sbWithCapacity.toString();long time3 = System.nanoTime() - start;System.out.printf("字符串+连接: %d ns%n", time1);System.out.printf("StringBuilder: %d ns%n", time2);System.out.printf("预设容量StringBuilder: %d ns%n", time3);System.out.printf("StringBuilder比+快: %.2f倍%n", (double)time1/time2);}// String.join()的使用public static void demonstrateStringJoin() {List<String> items = Arrays.asList("apple", "banana", "orange", "grape");// 使用String.join()long start = System.nanoTime();String joined1 = String.join(", ", items);long time1 = System.nanoTime() - start;// 使用StringBuilderstart = System.nanoTime();StringBuilder sb = new StringBuilder();for (int i = 0; i < items.size(); i++) {if (i > 0) sb.append(", ");sb.append(items.get(i));}String joined2 = sb.toString();long time2 = System.nanoTime() - start;System.out.println("String.join结果: " + joined1);System.out.println("StringBuilder结果: " + joined2);System.out.printf("String.join耗时: %d ns%n", time1);System.out.printf("StringBuilder耗时: %d ns%n", time2);}// StringBuilder容量优化public static void demonstrateStringBuilderCapacity() {int iterations = 1000;// 默认容量(会发生多次扩容)long start = System.nanoTime();StringBuilder sb1 = new StringBuilder();for (int i = 0; i < iterations; i++) {sb1.append("This is a longer string for testing capacity ");}long time1 = System.nanoTime() - start;// 预设充足容量(避免扩容)start = System.nanoTime();StringBuilder sb2 = new StringBuilder(iterations * 50);for (int i = 0; i < iterations; i++) {sb2.append("This is a longer string for testing capacity ");}long time2 = System.nanoTime() - start;System.out.printf("默认容量StringBuilder: %d ns%n", time1);System.out.printf("预设容量StringBuilder: %d ns%n", time2);System.out.printf("预设容量提升: %.2f倍%n", (double)time1/time2);}// 字符串缓存private static final Map<String, String> stringCache = new HashMap<>();public static void demonstrateStringCaching() {// 模拟重复的字符串处理String[] inputs = {"user123", "admin", "user123", "guest", "admin", "user123"};// 不使用缓存long start = System.nanoTime();for (String input : inputs) {String processed = processString(input);}long time1 = System.nanoTime() - start;// 使用缓存start = System.nanoTime();for (String input : inputs) {String processed = getCachedProcessedString(input);}long time2 = System.nanoTime() - start;System.out.printf("不使用缓存: %d ns%n", time1);System.out.printf("使用缓存: %d ns%n", time2);}private static String processString(String input) {// 模拟复杂的字符串处理return input.toUpperCase() + "_PROCESSED_" + input.length();}private static String getCachedProcessedString(String input) {return stringCache.computeIfAbsent(input, StringOptimization::processString);}
}
9. 常见陷阱和注意事项
9.1 空指针异常陷阱
public class StringNullPointerTraps {public static void main(String[] args) {// 陷阱1:空字符串vs nullString nullStr = null;String emptyStr = "";// 错误的做法try {System.out.println(nullStr.length()); // NullPointerException} catch (NullPointerException e) {System.out.println("捕获到空指针异常");}// 正确的做法safeStringOperations(nullStr);safeStringOperations(emptyStr);safeStringOperations("normal string");// 字符串比较的安全做法demonstrateSafeComparison();}public static void safeStringOperations(String str) {// 方法1:传统检查if (str != null && !str.isEmpty()) {System.out.println("字符串长度: " + str.length());} else {System.out.println("字符串为null或空");}// 方法2:使用Objects.isNull()if (!Objects.isNull(str) && !str.trim().isEmpty()) {System.out.println("字符串内容: " + str.trim());}// 方法3:使用Optional(Java 8+)Optional.ofNullable(str).filter(s -> !s.trim().isEmpty()).ifPresent(s -> System.out.println("Optional处理: " + s));}public static void demonstrateSafeComparison() {String str1 = null;String str2 = "Hello";// 危险的比较// boolean result = str1.equals(str2); // NullPointerException// 安全的比较方法System.out.println("Objects.equals: " + Objects.equals(str1, str2));System.out.println("常量在前: " + "Hello".equals(str1));// 忽略大小写的安全比较System.out.println("安全忽略大小写: " + Objects.equals(Optional.ofNullable(str1).map(String::toLowerCase).orElse(""), Optional.ofNullable(str2).map(String::toLowerCase).orElse("")));}
}
9.2 字符编码陷阱
public class StringEncodingTraps {public static void main(String[] args) {try {// 字符编码问题String original = "你好,世界!";// 不同编码的字节数组byte[] utf8Bytes = original.getBytes("UTF-8");byte[] gbkBytes = original.getBytes("GBK");byte[] isoBytes = original.getBytes("ISO-8859-1");System.out.println("UTF-8字节长度: " + utf8Bytes.length);System.out.println("GBK字节长度: " + gbkBytes.length);System.out.println("ISO-8859-1字节长度: " + isoBytes.length);// 错误的解码方式String wrongDecoded = new String(utf8Bytes, "GBK");System.out.println("错误解码: " + wrongDecoded);// 正确的解码方式String correctDecoded = new String(utf8Bytes, "UTF-8");System.out.println("正确解码: " + correctDecoded);// 平台默认编码System.out.println("默认字符集: " + Charset.defaultCharset());// 安全的编码转换safeEncodingConversion();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}public static void safeEncodingConversion() {String text = "测试中文编码";try {// 明确指定编码byte[] bytes = text.getBytes(StandardCharsets.UTF_8);String decoded = new String(bytes, StandardCharsets.UTF_8);System.out.println("安全编码转换: " + decoded);// 处理文件读写时的编码writeAndReadWithEncoding(text);} catch (Exception e) {e.printStackTrace();}}public static void writeAndReadWithEncoding(String text) throws IOException {Path tempFile = Files.createTempFile("test", ".txt");// 写入时指定编码Files.write(tempFile, text.getBytes(StandardCharsets.UTF_8));// 读取时指定编码String readText = new String(Files.readAllBytes(tempFile), StandardCharsets.UTF_8);System.out.println("文件读写测试: " + readText);// 清理临时文件Files.deleteIfExists(tempFile);}
}
9.3 性能陷阱
public class StringPerformanceTraps {public static void main(String[] args) {// 陷阱1:循环中的字符串连接demonstrateConcatenationTrap();// 陷阱2:不必要的String对象创建demonstrateUnnecessaryObjectCreation();// 陷阱3:错误使用substring()demonstrateSubstringTrap();// 陷阱4:正则表达式的重复编译demonstrateRegexTrap();}public static void demonstrateConcatenationTrap() {int count = 1000;// 陷阱:在循环中使用+连接long start = System.nanoTime();String result = "";for (int i = 0; i < count; i++) {result += "item" + i; // 每次都创建新对象}long trapTime = System.nanoTime() - start;// 正确:使用StringBuilderstart = System.nanoTime();StringBuilder sb = new StringBuilder(count * 10);for (int i = 0; i < count; i++) {sb.append("item").append(i);}String correctResult = sb.toString();long correctTime = System.nanoTime() - start;System.out.printf("字符串连接陷阱耗时: %d ns%n", trapTime);System.out.printf("正确做法耗时: %d ns%n", correctTime);System.out.printf("性能提升: %.2f倍%n", (double)trapTime/correctTime);}public static void demonstrateUnnecessaryObjectCreation() {// 陷阱:不必要的String对象创建String str = "Hello World";// 错误:创建不必要的对象String wrong = new String(str); // 创建了新对象// 正确:直接使用String correct = str; // 引用同一个对象System.out.println("str == wrong: " + (str == wrong)); // falseSystem.out.println("str == correct: " + (str == correct)); // true// 另一个例子:toString()的使用String value = "test";String unnecessary = value.toString(); // 不必要String direct = value; // 直接使用System.out.println("value == unnecessary: " + (value == unnecessary)); // trueSystem.out.println("value == direct: " + (value == direct)); // true}public static void demonstrateSubstringTrap() {// Java 7之前的substring内存泄漏问题(现已修复)String largeString = "x".repeat(1000000); // 创建大字符串// 在Java 6中,这会导致内存泄漏String smallPart = largeString.substring(0, 10);// Java 7+中已修复,但了解这个历史问题很重要System.out.println("小部分长度: " + smallPart.length());// 如果需要完全独立的字符串(在老版本Java中)String independent = new String(smallPart);System.out.println("独立字符串: " + independent);}public static void demonstrateRegexTrap() {String text = "Hello123World456";int iterations = 1000;// 陷阱:重复编译正则表达式long start = System.nanoTime();for (int i = 0; i < iterations; i++) {String[] parts = text.split("\\d+"); // 每次都编译正则}long trapTime = System.nanoTime() - start;// 正确:预编译正则表达式Pattern pattern = Pattern.compile("\\d+");start = System.nanoTime();for (int i = 0; i < iterations; i++) {String[] parts = pattern.split(text);}long correctTime = System.nanoTime() - start;System.out.printf("重复编译正则耗时: %d ns%n", trapTime);System.out.printf("预编译正则耗时: %d ns%n", correctTime);System.out.printf("性能提升: %.2f倍%n", (double)trapTime/correctTime);}
}
10. 实际应用示例
10.1 文本处理工具类
public class TextUtils {/*** 判断字符串是否为空或只包含空白字符*/public static boolean isBlank(String str) {return str == null || str.trim().isEmpty();}/*** 安全的字符串截取,防止索引越界*/public static String safeSsubstring(String str, int start, int end) {if (str == null) return null;if (start < 0) start = 0;if (end > str.length()) end = str.length();if (start >= end) return "";return str.substring(start, end);}/*** 将字符串首字母大写*/public static String capitalize(String str) {if (isBlank(str)) return str;return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();}/*** 驼峰命名转下划线*/public static String camelToUnderscore(String camelCase) {if (isBlank(camelCase)) return camelCase;return camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();}/*** 下划线转驼峰命名*/public static String underscoreToCamel(String underscore) {if (isBlank(underscore)) return underscore;String[] parts = underscore.split("_");StringBuilder sb = new StringBuilder(parts[0].toLowerCase());for (int i = 1; i < parts.length; i++) {sb.append(capitalize(parts[i]));}return sb.toString();}/*** 脱敏处理(如手机号、身份证号)*/public static String mask(String str, int start, int end, char maskChar) {if (isBlank(str) || start >= end || start >= str.length()) {return str;}StringBuilder sb = new StringBuilder(str);int actualEnd = Math.min(end, str.length());for (int i = start; i < actualEnd; i++) {sb.setCharAt(i, maskChar);}return sb.toString();}/*** 检查字符串是否为有效邮箱格式*/public static boolean isValidEmail(String email) {if (isBlank(email)) return false;return email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");}/*** 生成指定长度的随机字符串*/public static String generateRandomString(int length) {String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";StringBuilder sb = new StringBuilder(length);Random random = new Random();for (int i = 0; i < length; i++) {sb.append(chars.charAt(random.nextInt(chars.length())));}return sb.toString();}public static void main(String[] args) {// 测试工具方法System.out.println("isBlank测试: " + isBlank(" ")); // trueSystem.out.println("safeSub测试: " + safeSubstring("Hello", 0, 10)); // HelloSystem.out.println("首字母大写: " + capitalize("hello world")); // Hello worldSystem.out.println("驼峰转下划线: " + camelToUnderscore("userName")); // user_nameSystem.out.println("下划线转驼峰: " + underscoreToCamel("user_name")); // userNameSystem.out.println("脱敏处理: " + mask("13812345678", 3, 7, '*')); // 138****5678System.out.println("邮箱验证: " + isValidEmail("test@example.com")); // trueSystem.out.println("随机字符串: " + generateRandomString(8));}
}
10.2 CSV文件处理示例
public class CSVProcessor {/*** 解析CSV行,处理引号和逗号*/public static List<String> parseCSVLine(String line) {List<String> result = new ArrayList<>();if (line == null || line.isEmpty()) {return result;}StringBuilder currentField = new StringBuilder();boolean inQuotes = false;for (int i = 0; i < line.length(); i++) {char c = line.charAt(i);if (c == '"') {if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') {// 处理双引号转义currentField.append('"');i++; // 跳过下一个引号} else {inQuotes = !inQuotes;}} else if (c == ',' && !inQuotes) {result.add(currentField.toString());currentField.setLength(0);} else {currentField.append(c);}}result.add(currentField.toString());return result;}/*** 创建CSV行,自动处理引号和逗号*/public static String createCSVLine(List<String> fields) {StringBuilder sb = new StringBuilder();for (int i = 0; i < fields.size(); i++) {if (i > 0) sb.append(',');String field = fields.get(i);if (field.contains(",") || field.contains("\"") || field.contains("\n")) {sb.append('"').append(field.replace("\"", "\"\"")).append('"');} else {sb.append(field);}}return sb.toString();}public static void main(String[] args) {// 测试CSV解析String csvLine = "张三,25,\"软件工程师,Java开发\",\"地址包含\"\"引号\"\"\"";List<String> fields = parseCSVLine(csvLine);System.out.println("解析结果: " + fields);// 测试CSV创建List<String> newFields = Arrays.asList("李四", "30", "产品经理,UI设计", "地址包含\"引号\"");String newCSVLine = createCSVLine(newFields);System.out.println("创建CSV: " + newCSVLine);}
}
10.3 模板引擎示例
public class SimpleTemplateEngine {private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");/*** 简单的字符串模板替换*/public static String render(String template, Map<String, Object> data) {if (template == null || data == null) {return template;}StringBuilder result = new StringBuilder();Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);int lastEnd = 0;while (matcher.find()) {// 添加占位符前的文本result.append(template, lastEnd, matcher.start());// 获取占位符中的键名String key = matcher.group(1);Object value = data.get(key);// 替换占位符if (value != null) {result.append(value.toString());} else {result.append(matcher.group()); // 保持原占位符}lastEnd = matcher.end();}// 添加剩余文本result.append(template, lastEnd, template.length());return result.toString();}/*** 支持默认值的模板渲染*/public static String renderWithDefaults(String template, Map<String, Object> data, String defaultValue) {Pattern pattern = Pattern.compile("\\$\\{([^}|]+)(?:\\|([^}]*))?\\}");Matcher matcher = pattern.matcher(template);StringBuilder result = new StringBuilder();int lastEnd = 0;while (matcher.find()) {result.append(template, lastEnd, matcher.start());String key = matcher.group(1);String defaultVal = matcher.group(2);Object value = data.get(key);if (value != null) {result.append(value.toString());} else if (defaultVal != null) {result.append(defaultVal);} else {result.append(defaultValue);}lastEnd = matcher.end();}result.append(template, lastEnd, template.length());return result.toString();}public static void main(String[] args) {// 基本模板测试String template = "你好,${name}!今天是${date},欢迎来到${place}。";Map<String, Object> data = new HashMap<>();data.put("name", "张三");data.put("date", "2023年12月1日");data.put("place", "北京");String result1 = render(template, data);System.out.println("基本模板: " + result1);// 带默认值的模板测试String templateWithDefaults = "用户:${username|游客},级别:${level|普通用户}";Map<String, Object> data2 = new HashMap<>();data2.put("username", "admin");// level未设置,会使用默认值String result2 = renderWithDefaults(templateWithDefaults, data2, "未知");System.out.println("默认值模板: " + result2);}
}
总结
String是Java中最重要的类之一,掌握其特性和正确使用方法对于编写高效的Java程序至关重要。
核心要点回顾:
- 不可变性:String对象创建后不能修改,任何修改都会创建新对象
- 字符串池:相同内容的字符串字面量共享内存空间
- 性能考虑:频繁字符串操作应使用StringBuilder/StringBuffer
- 比较操作:内容比较用equals(),引用比较用==
- 空值处理:注意null安全的编程实践
- 编码问题:明确指定字符编码,避免乱码
- 正则表达式:预编译Pattern提高性能
最佳实践:
- 优先使用字符串字面量而不是new String()
- 大量字符串拼接使用StringBuilder
- 多线程环境考虑使用StringBuffer
- 进行null检查避免NullPointerException
- 使用String.join()进行批量连接
- 预设StringBuilder的初始容量
- 缓存经常使用的字符串处理结果