1. 概述
在 Java 中判断一个字符串是否包含另一个子串,方法有很多。本文聚焦于如何实现不区分大小写的子串匹配,也就是 String.contains()
的忽略大小写版本。我们会介绍几种常见方案,并结合代码示例说明各自的优劣。
✅ 目标:判断
src.contains(dest)
,但忽略大小写
❌ 不推荐:手动遍历字符逐个比较(效率低,没必要造轮子)
2. 最简单的方案:String.toLowerCase
最直观、最简单粗暴的方式,就是先把两个字符串都转成小写,再调用 contains()
:
assertTrue(src.toLowerCase().contains(dest.toLowerCase()));
同理,你也可以用 toUpperCase()
,效果一样:
assertTrue(src.toUpperCase().contains(dest.toUpperCase()));
✅ 优点:代码简洁,无需额外依赖
⚠️ 缺点:会创建新的字符串对象,在高频调用场景下可能带来 GC 压力
📌 适用场景:简单逻辑、低频调用、对性能不敏感的业务代码
3. 使用正则表达式:String.matches
String.matches()
支持正则表达式,可以通过 (?i)
标志开启忽略大小写模式:
assertTrue(src.matches("(?i).*" + dest + ".*"));
(?i)
:开启不区分大小写的匹配.*
:匹配任意数量的字符(除换行符外)
✅ 优点:一行搞定,语法灵活
❌ 缺点:
- 每次调用都会编译正则,性能较差
- 如果
dest
包含正则特殊字符(如.
,*
,?
),会导致意料之外的行为(比如.
匹配任意字符) - 可读性一般,容易被新手踩坑
📌 建议:仅用于简单场景或一次性匹配,不建议在循环中使用。
4. 使用 String.regionMatches
regionMatches
是 JDK 原生提供的区域匹配方法,支持忽略大小写:
public static boolean processRegionMatches(String src, String dest) {
for (int i = src.length() - dest.length(); i >= 0; i--)
if (src.regionMatches(true, i, dest, 0, dest.length()))
return true;
return false;
}
assertTrue(processRegionMatches(src, dest));
参数说明:
- 第一个
true
:表示忽略大小写 i
:源字符串的起始位置0
:目标字符串的起始位置dest.length()
:比较的字符长度
✅ 优点:不创建新字符串,内存友好
⚠️ 缺点:需要手动遍历,代码稍显冗长;性能并不突出(见第7节)
📌 提示:这个方法从后往前遍历是为了尽早结束(减少循环次数),属于一种简单优化。
5. 使用 Pattern
+ CASE_INSENSITIVE
标志
这是性能最好的方案之一。利用 Pattern
编译正则模式,并启用 CASE_INSENSITIVE
选项:
assertTrue(Pattern.compile(Pattern.quote(dest), Pattern.CASE_INSENSITIVE)
.matcher(src)
.find());
关键点解析:
Pattern.quote(dest)
:将dest
中的所有特殊字符转义,确保按字面量匹配Pattern.CASE_INSENSITIVE
:忽略大小写.find()
:查找是否有匹配的子串
✅ 优点:
- 性能最优(见下文)
- 安全处理特殊字符
- 可缓存
Pattern
对象,进一步提升重复匹配效率
📌 推荐做法(高频调用场景):
private static final Pattern PATTERN = Pattern.compile(
Pattern.quote("example"),
Pattern.CASE_INSENSITIVE
);
// 多次复用
boolean found = PATTERN.matcher(src).find();
6. 使用 Apache Commons StringUtils.containsIgnoreCase
如果你的项目已经引入了 Apache Commons Lang,可以直接使用现成的工具类:
assertTrue(StringUtils.containsIgnoreCase(src, dest));
✅ 优点:
- 语义清晰,代码可读性强
- 内部实现做了优化,比手写
toLowerCase
更高效 - 开箱即用,无需自己封装
⚠️ 缺点:需要额外依赖 commons-lang3
📌 Maven 依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
7. 性能对比
我们使用 JMH(Java Microbenchmark Harness)对上述方法进行了微基准测试,结果如下(单位:纳秒,数值越小越好):
方法 | 平均耗时 (ns) |
---|---|
Pattern + CASE_INSENSITIVE |
✅ 399.387 |
String.toLowerCase |
434.064 |
Apache Commons StringUtils |
496.313 |
String.regionMatches |
718.842 |
String.matches + 正则 |
❌ 3964.346 |
结论:
- 🏆 冠军:
Pattern.compile(..., CASE_INSENSITIVE)
,性能最佳 - 🥈 第二名:
toLowerCase()
,简单但稍慢 - ⚠️ 最差:
String.matches
,慢了近 10 倍,慎用!
💡 补充:从 Java 8 到 Java 11,
toLowerCase()
和Pattern
的性能均有明显提升,建议使用较新版本 JDK。
8. 总结
本文介绍了 5 种实现不区分大小写的字符串包含判断的方法:
方案 | 是否推荐 | 场景建议 |
---|---|---|
toLowerCase() |
✅ | 简单场景,低频调用 |
Pattern + CASE_INSENSITIVE |
✅✅✅ | 高频匹配、性能敏感场景 |
StringUtils.containsIgnoreCase |
✅✅ | 已引入 Commons 的项目 |
regionMatches |
⚠️ | 了解即可,性能一般 |
String.matches |
❌ | 不推荐,性能差 |
最佳实践建议:
- 🔹 **优先使用
Pattern
+CASE_INSENSITIVE
**,尤其是需要高性能或频繁匹配的场景 - 🔹 如果项目已有
commons-lang3
,直接用StringUtils.containsIgnoreCase()
,简洁安全 - 🔹 避免在循环中使用
String.matches
所有示例代码已上传至 GitHub:https://github.com/example-java/string-case-insensitive-demo