1. 概述
Java 中的 String.trim()
方法可以同时去除字符串首尾的空白字符,但标准库并没有提供仅去除左侧(L-Trim)或右侧(R-Trim)空白的原生方法。
本文将介绍几种实现 L-Trim 和 R-Trim 的方式,并通过性能对比帮你选出最适合的方案。✅
⚠️ 注意:本文讨论的是“仅去左”或“仅去右”,不是
trim()
那种双向去除。
2. 使用 while 循环(最直接)
最简单粗暴的方式就是手动遍历字符,找到第一个非空白位。
L-Trim 实现
从左往右扫描,直到遇到非空白字符为止:
int i = 0;
while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
i++;
}
String ltrim = s.substring(i);
R-Trim 实现
从右往左扫描,跳过尾部空白:
int i = s.length() - 1;
while (i >= 0 && Character.isWhitespace(s.charAt(i))) {
i--;
}
String rtrim = s.substring(0, i + 1);
✅ 优点:逻辑清晰、无依赖、性能优秀
❌ 缺点:代码略冗长,需手动封装成工具方法
3. 使用 String.replaceAll + 正则表达式
利用正则表达式也能一行搞定:
String ltrim = src.replaceAll("^\\s+", "");
String rtrim = src.replaceAll("\\s+$", "");
^\\s+
:匹配行首的一个或多个空白字符\\s+$
:匹配行尾的一个或多个空白字符
⚠️ 注意:这里的 \\s
不仅包括空格,还包括 \t
, \n
, \r
, \f
等 Unicode 空白字符,与 Character.isWhitespace()
行为一致。
✅ 优点:代码简洁,适合临时脚本
❌ 缺点:每次调用都会编译正则,频繁使用时性能较差
4. 使用 Pattern.compile + matcher(复用正则)
如果需要高频调用,建议将正则表达式预编译,避免重复解析:
private static final Pattern LTRIM = Pattern.compile("^\\s+");
private static final Pattern RTRIM = Pattern.compile("\\s+$");
String ltrim = LTRIM.matcher(s).replaceAll("");
String rtrim = RTRIM.matcher(s).replaceAll("");
✅ 优点:正则只编译一次,性能优于 String.replaceAll
❌ 缺点:仍不如纯字符遍历快,且占用静态内存
5. 使用 Apache Commons Lang
Apache Commons 是 Java 生态中最常用的工具库之一,其 StringUtils
提供了专门的接口:
添加依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
使用 stripStart 和 stripEnd
String ltrim = StringUtils.stripStart(src, null);
String rtrim = StringUtils.stripEnd(src, null);
传
null
表示按默认空白字符处理(等价于Character.isWhitespace
)
✅ 优点:
- API 设计清晰
- 经过大量生产验证
- 支持自定义字符集(第二个参数可指定要去除的字符)
⚠️ 建议:项目中已有 Commons 依赖时,首选方案。
6. 使用 Google Guava
Guava 作为另一个主流工具库,提供了更语义化的 API:
添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
使用 CharMatcher
String ltrim = CharMatcher.whitespace().trimLeadingFrom(s);
String rtrim = CharMatcher.whitespace().trimTrailingFrom(s);
✅ 优点:
- API 极其清晰,“trimLeadingFrom”一看就懂
CharMatcher
功能强大,可扩展性好(比如自定义匹配规则)
❌ 缺点:相比 Commons,Guava 更重,如果不是 already in use,不建议仅为 trim 引入
7. 性能对比(JMH 基准测试)
我们使用 JMH 对上述方案进行性能压测,测试字符串为:
src = " White spaces left and right ";
7.1 测试配置
@Fork(5)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
每个 benchmark 执行 L-Trim + R-Trim 并校验结果。
7.2 各方案性能数据(单位:ns/op)
方案 | 平均耗时 (ns) | 排名 |
---|---|---|
while 循环 |
110.379 | 🥇 |
Apache Commons | 108.718 | 🥇 |
Guava | 113.601 | 🥇 |
Pattern.matcher | 850.085 | 🟨 |
String.replaceAll | 1046.660 | 🟥 |
7.3 结果分析
- ✅ 前三甲几乎打平:
while
、Commons、Guava 性能非常接近,差异在误差范围内 - ⚠️ 正则方案慢一个数量级:尤其是
String.replaceAll
,每次都重新编译正则,代价高 - 🔁
Pattern.compile
复用后性能提升明显,但仍远不如前三种
💡 结论:纯字符遍历和主流工具库性能最佳,正则适合低频场景。
8. 总结与建议
方案 | 推荐场景 |
---|---|
✅ while 循环 |
无外部依赖、追求极致性能、可封装成工具类 |
✅ Apache Commons | 项目已引入 commons-lang3 ,首选方案 |
✅ Guava | 已使用 Guava 生态,API 更优雅 |
⚠️ Pattern.compile |
高频使用正则且需复用时 |
❌ String.replaceAll |
仅用于一次性脚本或低频调用 |
最终代码示例已托管至 GitHub:
👉 https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-string-operations-2
踩坑提醒:别再用
replaceAll
做 trim 操作了,性能差还容易被误用!