substring()
是 Java String
类中最常用的字符串截取方法,用于从原字符串中提取指定范围的子串。它简单高效,是字符串处理的基石操作之一。
方法定义
String
类提供了两个重载的 substring()
方法:
// 1. 从指定索引开始截取到字符串末尾
public String substring(int beginIndex)
// 2. 截取指定起始索引到结束索引之间的子串(左闭右开)
public String substring(int beginIndex, int endIndex)
返回值:
- 返回一个新的
String
对象,表示截取的子字符串。 - 原字符串保持不变(
String
不可变性)。
参数说明:
beginIndex
:起始索引(包含),从 0 开始。endIndex
:结束索引(不包含),即子串的结束位置。
功能说明
substring()
的核心功能是从原字符串中提取一个连续的字符序列。
- 索引规则: 索引从
0
开始,length() - 1
结束。 - 区间性质:
[beginIndex, endIndex)
—— 左闭右开区间。 - 共享字符数组(JDK 7 之前): 早期版本中,
substring()
返回的字符串可能共享原字符串的底层char[]
,导致内存泄漏风险(见注意事项)。 - 独立副本(JDK 7+): 从 JDK 7 Update 6 开始,
substring()
返回的是新创建的字符串,不再共享底层数组,避免了内存泄漏问题。
示例代码
基础用法
public class SubstringExample {
public static void main(String[] args) {
String text = "Hello, Java Programming!";
// 从索引 7 开始截取到末尾
System.out.println(text.substring(7)); // 输出: Java Programming!
// 截取索引 7 到 11 的子串(不包含 11)
System.out.println(text.substring(7, 11)); // 输出: Java
// 截取前 5 个字符
System.out.println(text.substring(0, 5)); // 输出: Hello
// 截取最后一个字符
System.out.println(text.substring(text.length() - 1)); // 输出: !
}
}
实用场景示例
1. 提取文件扩展名
String fileName = "document.pdf";
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex != -1) {
String extension = fileName.substring(dotIndex + 1);
System.out.println("Extension: " + extension); // 输出: pdf
}
2. 提取 URL 路径
String url = "https://www.example.com/users/profile";
int start = url.indexOf("://") + 3;
int end = url.indexOf('/', start);
String domain = url.substring(start, end); // "www.example.com"
System.out.println("Domain: " + domain);
3. 截取前缀或后缀
String code = "PREFIX_12345";
if (code.startsWith("PREFIX_")) {
String id = code.substring(7); // 去掉前缀
System.out.println("ID: " + id); // 输出: 12345
}
使用技巧
安全截取(避免越界)
在调用前检查索引有效性,或使用Math.min()
/Math.max()
控制范围。public static String safeSubstring(String str, int start, int end) { if (str == null || start >= str.length()) return ""; start = Math.max(0, start); end = Math.min(str.length(), end); return str.substring(start, end); }
结合
indexOf()
和lastIndexOf()
动态确定截取边界,常用于解析结构化文本。String data = "name: Alice, age: 25"; int start = data.indexOf(": ") + 2; int end = data.indexOf(",", start); String name = data.substring(start, end); // "Alice"
截取固定长度(或到末尾)
防止StringIndexOutOfBoundsException
。String longText = "Short"; int maxLength = 10; String display = longText.length() > maxLength ? longText.substring(0, maxLength) + "..." : longText;
去除首尾字符
String quoted = "\"Hello\""; String unquoted = quoted.substring(1, quoted.length() - 1); // Hello
常见错误
索引越界 (
StringIndexOutOfBoundsException
)String s = "Hi"; s.substring(5); // 错误:起始索引超出长度 s.substring(1, 5); // 错误:结束索引超出长度
纠正: 确保
0 <= beginIndex <= endIndex <= str.length()
。beginIndex > endIndex
"Hello".substring(3, 1); // 抛出 StringIndexOutOfBoundsException
忽略
null
值String input = null; input.substring(2); // NullPointerException
纠正: 调用前判空。
误解“结束索引”为包含
"abcdef".substring(0, 3) // 期望 "abcd"?实际是 "abc"(索引 0,1,2)
注意事项
JDK 版本差异(重要!)
- JDK 6 及之前:
substring()
返回的字符串共享原字符串的char[]
。如果原字符串很大,仅截取一小段,但新字符串被长期持有,会导致大数组无法回收,造成内存泄漏。 - JDK 7+:
substring()
创建新的char[]
数组,不再共享,解决了内存泄漏问题。这是升级的重要原因之一。
- JDK 6 及之前:
返回新字符串对象
每次调用都可能创建新对象(JDK 7+),有轻微性能开销。空字符串处理
"".substring(0); // 返回 "" (空字符串) "a".substring(0, 0); // 返回 "" (有效范围)
不可变性
原字符串内容不会被修改。
最佳实践与性能优化
优先使用
String
内建方法
substring()
是标准且高效的方式,无需手动遍历。避免对超长字符串频繁截取
虽然 JDK 7+ 已优化,但大量截取仍会产生多个对象,考虑使用StringBuilder
或流式处理。结合正则表达式处理复杂模式
对非固定位置的提取,使用Pattern
和Matcher
更灵活。使用
CharSequence
提高兼容性
方法参数可声明为CharSequence
,支持String
、StringBuilder
、StringBuffer
。性能对比(JDK 8+) | 操作 | 推荐方式 | |---|---| | 简单截取 |
substring()
| | 多次拼接截取 |StringBuilder
| | 复杂模式提取 |Pattern
/Matcher
| | 忽略大小写截取 | 先toLowerCase()
再substring()
|替代方案考虑
String#split()
:适用于分隔符明确的拆分。String#replace()
/replaceAll()
:用于替换而非截取。String#trim()
:去除首尾空白。
总结
String.substring()
是 Java 字符串操作的核心方法,掌握其使用是开发者的必备技能。
✅ 核心要点:
substring(begin)
:从begin
到末尾。substring(begin, end)
:左闭右开[begin, end)
。- 返回新字符串,原串不变。
- JDK 7+ 已解决内存泄漏问题。
- 索引越界会抛出异常,需注意边界检查。
🚀 实践建议:
- 简单截取首选
substring()
。 - 动态边界结合
indexOf()
等方法。 - 注意索引范围,防止越界。
- JDK 6 项目需警惕内存泄漏风险。
substring()
看似简单,却是构建复杂字符串逻辑的基石。熟练掌握它,能让你的代码更简洁、高效、可靠。