String类是Java语言中最基础、使用频率最高的类之一,它代表不可变的字符序列。在实际开发中,几乎所有的Java程序都会用到String类来处理文本信息。与基本数据类型不同,String是一个真正的类,提供了丰富的方法来操作字符串。
String类的不可变性是其最显著的特性。这意味着一旦一个String对象被创建,它的值就不能被改变。任何看似修改字符串的操作(如拼接、替换等),实际上都是创建了一个新的String对象。这种设计带来了几个重要影响:
重要提示:理解String的不可变性是掌握其API的关键。所有"修改"操作都会产生新对象,这在内存敏感的场景需要特别注意。
Java中创建String对象主要有两种方式:
String s1 = "hello";String s2 = new String("hello");这两种方式在内存分配上有本质区别:
java复制String a = "hello"; // 常量池创建
String b = "hello"; // 直接引用常量池
String c = new String("hello"); // 堆内存新建对象
System.out.println(a == b); // true,同一对象
System.out.println(a == c); // false,不同对象
System.out.println(a.equals(c)); // true,内容相同
字符串常量池(String Pool)是JVM为了提高性能、减少内存消耗而设计的一种特殊存储区域。在Java 7之前,它位于方法区(永久代);从Java 7开始,字符串常量池被移到了堆内存中。
这种变化带来了两个主要影响:
String类提供了多种比较方法,适用于不同场景:
equals(Object anObject):严格的内容比较,区分大小写equalsIgnoreCase(String anotherString):忽略大小写的内容比较compareTo(String anotherString):按字典顺序比较,返回int值contentEquals(CharSequence cs):与任何CharSequence实现比较java复制String s1 = "Java";
String s2 = "java";
String s3 = "Python";
System.out.println(s1.equals(s2)); // false
System.out.println(s1.equalsIgnoreCase(s2)); // true
System.out.println(s1.compareTo(s3)); // 负数,因为'J'<'P'
String类提供了丰富的查找和截取方法:
charAt(int index):获取指定位置的字符indexOf(String str):查找子串首次出现位置lastIndexOf(String str):查找子串最后出现位置substring(int beginIndex):从指定位置截取到末尾substring(int beginIndex, int endIndex):截取指定区间java复制String text = "Hello, Java programmers!";
System.out.println(text.charAt(7)); // 'J'
System.out.println(text.indexOf("Java")); // 7
System.out.println(text.substring(7, 11)); // "Java"
String类支持多种转换操作:
toLowerCase() / toUpperCase():大小写转换trim():去除首尾空白字符replace(char oldChar, char newChar):字符替换replaceAll(String regex, String replacement):正则替换java复制String original = " Hello, Java! ";
System.out.println(original.trim().toUpperCase()); // "HELLO, JAVA!"
System.out.println(original.replace('l', 'L')); // " HeLLo, Java! "
在Java中,字符串拼接有多种方式,性能差异显著:
+运算符:编译时优化为StringBuilder,但在循环中性能差concat()方法:每次调用都创建新String对象StringBuilder:可变字符序列,适合频繁修改StringBuffer:线程安全版的StringBuilderjava复制// 不推荐 - 每次循环都创建新StringBuilder对象
String result = "";
for (int i = 0; i < 100; i++) {
result += i;
}
// 推荐方式 - 单StringBuilder实例
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
String optimizedResult = sb.toString();
intern()方法将其加入常量池java复制String s1 = new String("hello").intern(); // 强制加入常量池
String s2 = "hello";
System.out.println(s1 == s2); // true
String类与字节数组转换时需要指定字符编码:
java复制String str = "你好,世界";
try {
byte[] utf8Bytes = str.getBytes("UTF-8");
String newStr = new String(utf8Bytes, "UTF-8");
System.out.println(newStr);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
关键注意:在不同平台间传输字符串数据时,必须明确指定编码方式,否则可能因平台默认编码不同导致乱码。
Java 8新增了方便的字符串拼接方法:
java复制String joined = String.join(", ", "Java", "Python", "C++");
System.out.println(joined); // "Java, Python, C++"
List<String> langs = Arrays.asList("Java", "Python", "C++");
System.out.println(String.join("|", langs)); // "Java|Python|C++"
提供对字符串字符的流式操作:
java复制"hello".chars()
.mapToObj(c -> (char)c)
.forEach(System.out::println);
java复制String s1 = "hello";
String s2 = new String("hello");
String s3 = s2.intern();
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // true
经验法则:内容比较总是使用equals(),除非你明确知道需要比较对象引用。
大字符串的substring()方法在Java 7之前可能导致内存泄漏,因为会共享原char数组。Java 7及以后版本已修复此问题,每次substring()都会创建新数组。
java复制// Java 6及以前版本需要注意
String huge = "...非常长的字符串...";
String small = huge.substring(0, 5); // 仍然引用原大数组
// 解决方案
String safeSmall = new String(huge.substring(0, 5));
String的split()和replaceAll()等方法使用正则表达式,复杂正则可能成为性能瓶颈:
java复制// 简单分割使用StringTokenizer更快
StringTokenizer st = new StringTokenizer("a,b,c", ",");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
java复制public static String formatLogMessage(String level, String message) {
LocalDateTime now = LocalDateTime.now();
return String.format("[%s] %tF %tT - %s",
level, now, now, message);
}
// 使用示例
String log = formatLogMessage("INFO", "Application started");
System.out.println(log);
// 输出示例: [INFO] 2023-05-20 14:30:45 - Application started
java复制public static boolean isValidUsername(String username) {
if (username == null || username.trim().isEmpty()) {
return false;
}
return username.matches("^[a-zA-Z0-9_]{4,16}$");
}
默认初始容量为16字符,扩容规则为:新容量 = 原容量 * 2 + 2
java复制StringBuilder sb = new StringBuilder(); // 初始容量16
sb.append("1234567890123456"); // 刚好16字符
sb.append("x"); // 触发扩容,新容量=16*2+2=34
性能提示:如果能预估最终大小,建议在构造时指定初始容量,避免多次扩容。
isBlank():检查字符串是否为空或仅包含空白字符lines():将字符串按行分割为Streamrepeat(int count):重复字符串指定次数strip():去除首尾空白(比trim()更严格)java复制String multiLine = "第一行\n第二行\n第三行";
multiLine.lines().forEach(System.out::println);
String heart = "❤".repeat(5);
System.out.println(heart); // ❤❤❤❤❤
Java 15引入了文本块语法,方便处理多行字符串:
java复制String html = """
<html>
<body>
<p>Hello, world!</p>
</body>
</html>
""";