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
}

使用技巧

  1. 安全截取(避免越界)
    在调用前检查索引有效性,或使用 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);
    }
    
  2. 结合 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"
    
  3. 截取固定长度(或到末尾)
    防止 StringIndexOutOfBoundsException

    String longText = "Short";
    int maxLength = 10;
    String display = longText.length() > maxLength ? 
                     longText.substring(0, maxLength) + "..." : 
                     longText;
    
  4. 去除首尾字符

    String quoted = "\"Hello\"";
    String unquoted = quoted.substring(1, quoted.length() - 1); // Hello
    

常见错误

  1. 索引越界 (StringIndexOutOfBoundsException)

    String s = "Hi";
    s.substring(5);        // 错误:起始索引超出长度
    s.substring(1, 5);     // 错误:结束索引超出长度
    

    纠正: 确保 0 <= beginIndex <= endIndex <= str.length()

  2. beginIndex > endIndex

    "Hello".substring(3, 1); // 抛出 StringIndexOutOfBoundsException
    
  3. 忽略 null

    String input = null;
    input.substring(2); // NullPointerException
    

    纠正: 调用前判空。

  4. 误解“结束索引”为包含

    "abcdef".substring(0, 3) // 期望 "abcd"?实际是 "abc"(索引 0,1,2)
    

注意事项

  1. JDK 版本差异(重要!)

    • JDK 6 及之前: substring() 返回的字符串共享原字符串的 char[]。如果原字符串很大,仅截取一小段,但新字符串被长期持有,会导致大数组无法回收,造成内存泄漏。
    • JDK 7+: substring() 创建新的 char[] 数组,不再共享,解决了内存泄漏问题。这是升级的重要原因之一。
  2. 返回新字符串对象
    每次调用都可能创建新对象(JDK 7+),有轻微性能开销。

  3. 空字符串处理

    "".substring(0); // 返回 "" (空字符串)
    "a".substring(0, 0); // 返回 "" (有效范围)
    
  4. 不可变性
    原字符串内容不会被修改。


最佳实践与性能优化

  1. 优先使用 String 内建方法
    substring() 是标准且高效的方式,无需手动遍历。

  2. 避免对超长字符串频繁截取
    虽然 JDK 7+ 已优化,但大量截取仍会产生多个对象,考虑使用 StringBuilder 或流式处理。

  3. 结合正则表达式处理复杂模式
    对非固定位置的提取,使用 PatternMatcher 更灵活。

  4. 使用 CharSequence 提高兼容性
    方法参数可声明为 CharSequence,支持 StringStringBuilderStringBuffer

  5. 性能对比(JDK 8+) | 操作 | 推荐方式 | |---|---| | 简单截取 | substring() | | 多次拼接截取 | StringBuilder | | 复杂模式提取 | Pattern/Matcher | | 忽略大小写截取 | 先 toLowerCase()substring() |

  6. 替代方案考虑

    • 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() 看似简单,却是构建复杂字符串逻辑的基石。熟练掌握它,能让你的代码更简洁、高效、可靠。