Java 字符串(String)详解

更新于 2025-12-25

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";

在这种情况下,myString1myString2 将指向同一个 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;
}

上述代码在每次迭代中都会创建新的 StringBuilderString 对象,并复制已有字符,导致时间复杂度呈 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)