1. 简介

在本篇教程中,我们将介绍几种从 String 中去除首尾指定字符的方法。为了便于说明,所有示例都以去除字符串中的前导和尾随零(zero)为例。

每种实现我们都提供两个方法:一个用于去除前导零,另一个用于去除尾随零。

这个问题有一个边界情况需要注意:如果输入字符串只包含零,我们是返回一个空字符串,还是保留一个 "0"?针对这两种需求,我们会在每种实现中分别给出对应的处理方式。

本文所有实现均配有单元测试,你可以在 GitHub 上找到这些测试代码。

2. 使用 StringBuilder

第一种方案是使用 StringBuilder 构造原始字符串,然后从开头或结尾逐个删除不需要的字符

String removeLeadingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 0 && sb.charAt(0) == '0') {
        sb.deleteCharAt(0);
    }
    return sb.toString();
}

String removeTrailingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 0 && sb.charAt(sb.length() - 1) == '0') {
        sb.setLength(sb.length() - 1);
    }
    return sb.toString();
}

注意,在删除尾随零时我们使用了 StringBuilder.setLength() 而不是 deleteCharAt(),因为后者效率较低。

如果我们 不想在全为零的情况下返回空字符串,只需要 在只剩一个字符时停止循环 即可:

String removeLeadingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 1 && sb.charAt(0) == '0') {
        sb.deleteCharAt(0);
    }
    return sb.toString();
}

String removeTrailingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 1 && sb.charAt(sb.length() - 1) == '0') {
        sb.setLength(sb.length() - 1);
    }
    return sb.toString();
}

✅ 优点:逻辑清晰,性能可接受
❌ 缺点:手动遍历效率略低

3. 使用 String.substring()

第二种方法是通过查找第一个或最后一个非零字符的位置,然后调用 substring() 截取剩余部分:

String removeLeadingZeroes(String s) {
    int index;
    for (index = 0; index < s.length(); index++) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(index);
}

String removeTrailingZeroes(String s) {
    int index;
    for (index = s.length() - 1; index >= 0; index--) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(0, index + 1);
}

⚠️ 注意:变量 index 需要定义在循环外部,以便后续使用。
⚠️ 注意:由于 indexOf()lastIndexOf() 只能精确匹配,我们只能手动查找非零字符。

如果 不想返回空字符串,只需修改循环条件即可:

String removeLeadingZeroes(String s) {
    int index;
    for (index = 0; index < s.length() - 1; index++) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(index);
}

String removeTrailingZeroes(String s) {
    int index;
    for (index = s.length() - 1; index > 0; index--) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(0, index + 1);
}

✅ 优点:逻辑直观,性能优于 StringBuilder
❌ 缺点:需要手动控制索引,稍显繁琐

4. 使用 Apache Commons

Apache Commons 提供了非常实用的工具类,其中 org.apache.commons.lang3.StringUtils 就包含我们需要的功能。

4.1. Maven 依赖

pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

4.2. 实现方式

StringUtils 提供了 stripStart()stripEnd() 方法,正好满足我们的需求:

String removeLeadingZeroes(String s) {
    return StringUtils.stripStart(s, "0");
}

String removeTrailingZeroes(String s) {
    return StringUtils.stripEnd(s, "0");
}

⚠️ 注意:这两个方法的第二个参数是字符集合,不是字符串匹配,比如传 "01" 会同时去除 '0''1'

如果 不想去除所有零,我们需要手动处理边界情况:

String removeLeadingZeroes(String s) {
    String stripped = StringUtils.stripStart(s, "0");
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

String removeTrailingZeroes(String s) {
    String stripped = StringUtils.stripEnd(s, "0");
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

✅ 优点:代码简洁,功能强大
❌ 缺点:需引入第三方依赖

5. 使用 Guava

Guava 也提供了类似的功能,使用 com.google.common.base.CharMatcher 可以轻松处理字符匹配。

5.1. Maven 依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

⚠️ Android 用户请使用 27.0-android 版本。

5.2. 实现方式

使用 trimLeadingFrom()trimTrailingFrom() 方法:

String removeLeadingZeroes(String s) {
    return CharMatcher.is('0').trimLeadingFrom(s);
}

String removeTrailingZeroes(String s) {
    return CharMatcher.is('0').trimTrailingFrom(s);
}

同样,如果不想全部去除,可手动判断:

String removeLeadingZeroes(String s) {
    String stripped = CharMatcher.is('0').trimLeadingFrom(s);
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

String removeTrailingZeroes(String s) {
    String stripped = CharMatcher.is('0').trimTrailingFrom(s);
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

✅ 优点:API 灵活,支持复杂匹配规则
❌ 缺点:依赖较重,学习成本略高

6. 使用正则表达式

既然是模式匹配问题,正则表达式自然也是不错的选择:

String removeLeadingZeroes(String s) {
    return s.replaceAll("^0+", "");
}

String removeTrailingZeroes(String s) {
    return s.replaceAll("0+$", "");
}

如果不想全部去除,可以结合断言(negative lookahead)来保留一个零:

String removeLeadingZeroes(String s) {
    return s.replaceAll("^0+(?!$)", "");
}

String removeTrailingZeroes(String s) {
    return s.replaceAll("(?!^)0+$", "");
}

⚠️ "(?!^)" 表示不是字符串开头,"(?!$)" 表示不是结尾。

✅ 优点:语法强大,一行搞定
❌ 缺点:正则表达式可读性差,维护成本高

7. 总结

本文介绍了多种去除字符串首尾指定字符的方法,包括:

  • 使用 StringBuilder 手动删除
  • 使用 substring() 截取
  • 使用 Apache Commons 的 StringUtils
  • 使用 Guava 的 CharMatcher
  • 使用正则表达式

每种方法都有其适用场景,选择哪种实现主要取决于个人偏好和项目约束。性能方面,substring() 通常优于 StringBuilder,而工具类方法则胜在简洁可靠。

一如既往,本文所有示例代码均可在 GitHub 获取。


原始标题:Remove Leading and Trailing Characters from a String