Jakob Jenkov 发布于 2020-04-29
Java 的 String 数据类型可以包含一串字符,就像串在绳子上的珍珠。字符串是 Java 中处理文本的方式。一旦创建了一个 Java 字符串,你就可以在其中搜索、从中创建子字符串、基于原字符串替换部分内容生成新字符串,以及执行许多其他操作。
内部字符串表示
在 Java 9 之前,Java 虚拟机内部使用 UTF-16 编码的字节来表示字符串。UTF-16 使用 2 个字节表示一个字符,因此 Java 字符串中的字符实际上是以 char 数组的形式存储的。
UTF 是一种能够表示多种语言(字母表)字符的编码方式。因此,为了在一个字符串中表示所有这些不同语言的字符,每个字符需要使用 2 个字节。
紧凑字符串(Compact Strings)
从 Java 9 开始,Java 虚拟机引入了一项名为 紧凑字符串(compact strings) 的优化特性。该特性允许 JVM 检测字符串是否仅包含 ISO-8859-1/Latin-1 字符。如果是,那么字符串内部将只使用 1 个字节来表示每个字符。此时,紧凑字符串的字符可以用 byte 数组而非 char 数组来表示。
是否可以使用紧凑字符串是在字符串创建时检测的。由于字符串一旦创建就不可变(immutable),因此这种优化是安全的。
创建字符串
Java 中的字符串是对象,因此通常需要使用 new 操作符来创建一个新的 String 对象。例如:
String myString = new String("Hello World");
引号内的文本就是该 String 对象所包含的内容。
Java 字符串字面量(String Literals)
Java 提供了一种更简洁的字符串创建方式:
String myString = "Hello World";
这种方式称为 字符串字面量(String literal)。Java 编译器会自动将其转换为对应的 String 对象。
转义字符(Escape Characters)
Java 字符串字面量支持一组转义字符,它们会被转换为字符串中的特殊字符。常见的转义字符包括:
| 转义字符 | 描述 |
|---|---|
\\ |
转换为单个反斜杠 \ |
\t |
转换为制表符(Tab) |
\r |
转换为回车符(Carriage Return) |
\n |
转换为换行符(New Line) |
示例:
String text = "\tThis text is one tab in.\r\n";
该字符串以一个制表符开头,以回车和换行符结尾。
字符串字面量作为常量或单例
如果你在多个地方使用相同的字符串字面量(例如 "Hello World"),Java 虚拟机可能会在内存中只创建一个 String 实例。这意味着多个变量实际上指向同一个对象。
示例:
String myString1 = "Hello World";
String myString2 = "Hello World";
在这种情况下,myString1 和 myString2 将指向同一个 String 对象。
更准确地说,这些字符串字面量是从 JVM 内部维护的 字符串常量池(String constant pool) 中获取的。这意味着即使来自不同项目但运行在同一应用中的类,也可能共享相同的字符串常量(在运行时共享,而非编译时)。
如果你希望确保两个字符串变量指向不同的对象,应使用 new 关键字:
String myString1 = new String("Hello World");
String myString2 = new String("Hello World");
尽管内容相同,JVM 会创建两个独立的对象。
Java 文本块(Text Blocks)
Java 13(预览功能)引入了 文本块(text blocks),也称为多行字符串,使得声明跨越多行的字符串更加方便。
语法示例:
String textblock = """
This is a text inside a text block
""";
注意首尾两行的三个连续双引号 """,它们是文本块的界定符。
在文本块中,你可以直接写入多行文本,而无需转义换行符或引号。例如:
String textblock = """
This is a text inside a text block.
You can use "quotes" in here without escaping them.
""";
只有当你需要在文本中包含三个连续的引号时,才需要对其中一个进行转义。
Java 文本块的缩进处理
文本块会自动处理缩进。JVM 通过查看结束界定符(最后一行的 """)的位置,来决定要从每行文本中去除多少前导空白。
示例:
String textblock1 = """
This is a Java text block
""";
String textblock2 = """
This is a Java text block
""";
String textblock3 = """
This is a Java text block
""";
输出结果分别为:
This is a Java text block
This is a Java text block
This is a Java text block
JVM 会根据结束界定符的缩进位置,保留相应数量的前导空格。
字符串拼接(Concatenating Strings)
字符串拼接是指将一个字符串附加到另一个字符串之后。由于 Java 字符串是不可变的,拼接操作会生成一个新的字符串对象。
示例:
String one = "Hello";
String two = "World";
String three = one + " " + two; // 结果为 "Hello World"
字符串拼接的性能问题
在循环中频繁拼接字符串可能导致性能问题。例如:
String[] strings = {"one", "two", "three"};
String result = null;
for (String s : strings) {
result = result + s;
}
上述代码在每次迭代中都会创建新的 StringBuilder 和 String 对象,并复制已有字符,导致时间复杂度呈 O(n²)。
推荐做法:在循环外创建一个 StringBuilder 并复用:
StringBuilder temp = new StringBuilder();
for (String s : strings) {
temp.append(s);
}
String result = temp.toString();
这样可避免不必要的对象创建和字符复制,显著提升性能。
获取字符串长度
使用 length() 方法可获取字符串的字符数(不是字节数):
String string = "Hello World";
int length = string.length(); // 11
子字符串(Substrings)
使用 substring(beginIndex, endIndex) 提取子串。注意:起始索引包含,结束索引不包含。
String string1 = "Hello World";
String substring = string1.substring(0, 5); // "Hello"
使用 indexOf() 搜索子串
indexOf() 返回子串首次出现的索引,未找到则返回 -1:
String string1 = "Hello World";
int index = string1.indexOf("World"); // 6
还可以指定起始搜索位置:
int index = theString.indexOf(substring, startIndex);
此外还有 lastIndexOf() 用于查找最后一次出现的位置。
使用正则表达式匹配:matches()
matches() 方法接受一个正则表达式,判断整个字符串是否匹配:
String text = "one two three two one";
boolean matches = text.matches(".*two.*"); // true
字符串比较方法
equals()
精确比较两个字符串(区分大小写):
"abc".equals("ABC"); // false
equalsIgnoreCase()
忽略大小写比较:
"abc".equalsIgnoreCase("ABC"); // true
startsWith() 和 endsWith()
检查字符串是否以某子串开头或结尾:
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
compareTo()
按字典顺序比较,返回负数、0 或正数:
"abc".compareTo("abd"); // -1
注意:
compareTo()不适用于非英语语言。如需本地化排序,请使用Collator。
去除首尾空白:trim()
trim() 移除字符串开头和结尾的空白字符(空格、制表符、换行符等):
String text = " Hello World ";
String trimmed = text.trim(); // "Hello World"
替换字符
replace(char old, char new)
替换所有匹配的字符:
"123abc".replace('a', '@'); // "123@bc"
replaceFirst(regex, replacement)
仅替换第一个匹配项(支持正则表达式):
"one two three two one".replaceFirst("two", "five");
// "one five three two one"
replaceAll(regex, replacement)
替换所有匹配项:
"one two three two one".replaceAll("two", "five");
// "one five three five one"
分割字符串:split()
使用正则表达式分割字符串为数组:
String source = "A man drove with a car.";
String[] parts = source.split("a");
// ["A m", "n drove with ", " c", "r."]
可指定最大分割数量(limit 参数):
source.split("a", 2);
// ["A m", "n drove with a car."]
数字转字符串:valueOf()
String intStr = String.valueOf(10); // "10"
String flStr = String.valueOf(9.99); // "9.99"
对象转字符串:toString()
所有对象都继承自 Object,因此都有 toString() 方法:
Integer i = new Integer(123);
String s = i.toString(); // "123"
注意:若类未重写
toString(),默认实现可能不具可读性。
获取字符和字节
charAt(index):获取指定位置的字符getBytes():获取字节表示(可指定编码,如 UTF-8)
String s = "Hello";
char c = s.charAt(0); // 'H'
byte[] bytes1 = s.getBytes(); // 默认编码
byte[] bytes2 = s.getBytes(StandardCharsets.UTF_8); // 显式指定 UTF-8
大小写转换
String s = "Hello World";
String upper = s.toUpperCase(); // "HELLO WORLD"
String lower = s.toLowerCase(); // "hello world"
字符串格式化(Java 13+ 预览)
formatted() 方法(与文本块一同引入):
String input = "Hello %s";
String output = input.formatted("World"); // "Hello World"
去除缩进:stripIndent()(Java 13+ 预览)
模拟文本块的缩进处理逻辑:
String input = " Hey\n This\n is\n indented.";
String output = input.stripIndent();
// 输出:
// Hey
// This
// is
// indented.
转义字符解析:translateEscapes()(Java 13+ 预览)
将字符串中的转义序列(如 \n)转换为实际字符:
String input = "Hey, \\n This is not normally a line break.";
String output = input.translateEscapes();
// 输出:
// Hey,
// This is not normally a line break.
其他方法
String 类还包含许多其他实用方法,详见 Java 官方文档(JavaDoc)。