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


原始标题:Case-Insensitive String Matching in Java

« 上一篇: Java Text Blocks 详解
» 下一篇: 使用Java入门OpenCV