在Java编程中,String类可能是最常用但又最容易被误解的类之一。作为不可变字符序列的代表,String类的每个方法调用实际上都会产生新的字符串对象。这种设计虽然保证了线程安全性,但也带来了性能上的考量。
String底层实际上是用final修饰的char数组存储数据,这也是其不可变特性的根源。当我们创建字符串时,JVM会先在字符串常量池中查找是否已存在相同内容的字符串,如果存在则直接返回引用,否则新建对象并放入池中。
java复制String str1 = "hello"; // 字符串常量池方式
String str2 = new String("hello"); // 堆内存新建对象
这两种创建方式有着本质区别:第一种会检查字符串常量池,第二种则强制在堆内存创建新对象。理解这个差异对内存优化至关重要。
字符串比较是最容易出错的操作之一。很多初学者会直接用==比较字符串,这实际上比较的是对象引用而非内容。
java复制String a = "java";
String b = new String("java");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
System.out.println(a.equalsIgnoreCase("JAVA")); // true
equals()方法会逐个比较字符内容,而compareTo()则会返回字符编码的差值。对于需要忽略大小写的场景,equalsIgnoreCase()是最佳选择。
indexOf()系列方法提供了灵活的查找能力:
java复制String text = "Java编程实战";
int pos1 = text.indexOf("编程"); // 4
int pos2 = text.lastIndexOf("a"); // 3
substring()方法需要注意参数边界:
java复制String str = "HelloWorld";
String sub1 = str.substring(5); // "World"
String sub2 = str.substring(0,5); // "Hello"
重要提示:substring()在JDK7前后的实现有重大变化,早期版本会共享原字符串的char数组,可能导致内存泄漏。
简单的+拼接在循环中会产生大量临时对象:
java复制// 低效写法
String result = "";
for(int i=0; i<100; i++){
result += i; // 每次循环创建新StringBuilder和String
}
// 高效写法
StringBuilder sb = new StringBuilder();
for(int i=0; i<100; i++){
sb.append(i);
}
String result = sb.toString();
StringBuilder在单线程环境下性能最佳,而StringBuffer则通过同步方法保证线程安全。
matches()方法可以快速验证字符串格式:
java复制String email = "test@example.com";
boolean isValid = email.matches("^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$");
split()方法支持正则分割:
java复制String data = "Java,Python,C++,JavaScript";
String[] langs = data.split(",\\s*"); // 处理可能存在的空格
getBytes()方法需要注意编码指定:
java复制String chinese = "中文";
byte[] utf8Bytes = chinese.getBytes(StandardCharsets.UTF_8);
byte[] gbkBytes = chinese.getBytes("GBK"); // 需处理UnsupportedEncodingException
valueOf()系列方法提供了各种类型的转换:
java复制String numStr = String.valueOf(123); // "123"
String boolStr = String.valueOf(true); // "true"
String doubleStr = String.valueOf(3.14); // "3.14"
parseXxx()方法则实现反向转换:
java复制int num = Integer.parseInt("456");
double val = Double.parseDouble("3.1415");
trim()只能去除ASCII空白字符:
java复制String withSpaces = " Hello ";
String trimmed = withSpaces.trim(); // "Hello"
JDK11引入的strip()可以处理Unicode空白字符:
java复制String withUnicodeSpaces = "\u2000Hello\u2000";
String stripped = withUnicodeSpaces.strip(); // "Hello"
format()方法提供了类似C语言的格式化能力:
java复制String msg = String.format("欢迎%s,当前积分:%d,折扣:%.2f",
"张三", 1500, 0.85);
// 结果:"欢迎张三,当前积分:1500,折扣:0.85"
intern()方法可以主动将字符串放入常量池:
java复制String s1 = new String("abc").intern();
String s2 = "abc";
System.out.println(s1 == s2); // true
但要注意过度使用可能导致常量池过大,反而影响性能。
对于复杂的字符串构建,可以采用模板模式:
java复制String template = "姓名:%s,年龄:%d,职业:%s";
String result = String.format(template, "李四", 28, "工程师");
或者使用MessageFormat:
java复制MessageFormat.format("日期:{0,date}, 金额:{1,number,currency}",
new Date(), 1250.5);
大字符串substring操作可能导致的内存泄漏:
java复制String bigString = "非常大的字符串内容...";
String smallPart = bigString.substring(0,10); // JDK6会持有bigString的引用
// 解决方案:
String safeSmall = new String(bigString.substring(0,10));
跨平台编码问题处理方案:
java复制// 错误写法
byte[] bytes = str.getBytes(); // 使用平台默认编码
// 正确写法
byte[] safeBytes = str.getBytes(StandardCharsets.UTF_8);
String recovered = new String(safeBytes, StandardCharsets.UTF_8);
贪婪匹配导致的性能问题:
java复制// 低效写法
String html = "<div>...</div>";
html.matches("<div>.*</div>"); // 回溯问题
// 高效写法
html.matches("<div>.*?</div>"); // 非贪婪匹配
多行字符串的现代写法:
java复制String json = """
{
"name": "张三",
"age": 28,
"skills": ["Java","Python"]
}
""";
align()方法简化格式处理:
java复制String text = """
Hello
World
""";
String aligned = text.align(0); // 去除公共缩进
在实际项目中,我习惯将字符串工具方法封装成StringUtils类,包含isEmpty、join、capitalize等常用操作。对于复杂的字符串处理,Apache Commons Lang中的StringUtils和Guava的Strings都提供了丰富的扩展方法。