1. 概述

在本教程中,我们将介绍几种判断一个字符串是否包含另一个子字符串的方法,并对它们的性能进行对比分析。


2. String.indexOf

首先,我们可以使用 String.indexOf 方法来判断子字符串是否存在。这个方法会返回子字符串首次出现的索引位置,如果没有找到则返回 -1。

举个例子:

Assert.assertEquals(9, "Bohemian Rhapsodyan".indexOf("Rhap"));

如果子字符串不存在,或者大小写不匹配,会返回 -1:

Assert.assertEquals(-1, "Bohemian Rhapsodyan".indexOf("rhap"));
Assert.assertEquals(9, "Bohemian Rhapsodyan".toLowerCase().indexOf("rhap"));

⚠️注意:indexOf 是区分大小写的。

如果查找的是 "an",它会返回第一个匹配的位置(索引为6):

Assert.assertEquals(6, "Bohemian Rhapsodyan".indexOf("an"));

3. String.contains

接下来是 String.contains 方法,它内部其实就是调用了 indexOf,但返回的是布尔值,更直观一些。

✅优点:代码简洁,逻辑清晰。

❌缺点:同样是区分大小写的。

示例:

Assert.assertTrue("Hey Ho, let's go".contains("Hey"));
Assert.assertFalse("Hey Ho, let's go".contains("jey"));
Assert.assertFalse("Hey Ho, let's go".contains("hey"));
Assert.assertTrue("Hey Ho, let's go".toLowerCase().contains("hey"));

⚠️注意:contains 内部调用的就是 indexOf,所以性能上几乎和 indexOf 是一样的。


4. StringUtils.containsIgnoreCase

如果你需要忽略大小写地查找子字符串,可以使用 Apache Commons Lang 提供的 StringUtils.containsIgnoreCase 方法。

✅优点:不区分大小写,适合模糊匹配。

❌缺点:性能略逊于前两种方法,因为它内部需要对字符串进行大小写转换后再比较。

示例:

Assert.assertTrue(StringUtils.containsIgnoreCase("Runaway train", "train"));
Assert.assertTrue(StringUtils.containsIgnoreCase("Runaway train", "Train"));

⚠️注意:虽然方便,但性能开销更大,适合对大小写不敏感的场景。


5. 使用 Pattern(正则表达式)

如果你需要更复杂的匹配逻辑,比如边界匹配、模式匹配等,可以使用 Java 的 PatternMatcher 类配合正则表达式。

示例:

Pattern pattern = Pattern.compile("(?<!\\S)" + "road" + "(?!\\S)");
Matcher matcher = pattern.matcher("Hit the road Jack");
Assert.assertTrue(matcher.find());

再看一个不匹配的例子:

matcher = pattern.matcher("and don't you come back no more");
Assert.assertFalse(matcher.find());

✅优点:灵活,支持正则表达式,适合复杂匹配。

❌缺点:性能最差,初始化 Pattern 和 Matcher 都需要额外开销。


6. 性能对比(使用 JMH)

我们使用 JMH(Java Microbenchmark Harness)来对上述方法进行性能测试。

6.1 测试环境准备

@Setup
public void setup() {
    message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " + 
      "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + 
      "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " + 
      "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " + 
      "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " + 
      "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + 
      "mollit anim id est laborum";
    pattern = Pattern.compile("(?<!\\S)" + "eiusmod" + "(?!\\S)");
}

6.2 各方法基准测试

  • indexOf

    @Benchmark
    public int indexOf() {
      return message.indexOf("eiusmod");
    }
    
  • contains

    @Benchmark
    public boolean contains() {
      return message.contains("eiusmod");
    }
    
  • StringUtils.containsIgnoreCase

    @Benchmark
    public boolean containsStringUtilsIgnoreCase() {
      return StringUtils.containsIgnoreCase(message, "eiusmod");
    }
    
  • Pattern.find

    @Benchmark
    public boolean searchWithPattern() {
      return pattern.matcher(message).find();
    }
    

6.3 性能结果分析(单位:纳秒)

方法名 平均耗时(ns)
contains 14.736
indexOf 14.200
containsStringUtilsIgnoreCase 385.632
searchWithPattern 1014.633

✅结论:

  • indexOf 最快,contains 紧随其后。
  • containsIgnoreCase 因为要处理大小写转换,性能下降明显。
  • Pattern.find 性能最差,适合复杂匹配,不适合简单查找。

7. 总结

我们介绍了几种判断字符串是否包含子字符串的方式:

方法名 是否区分大小写 性能表现 适用场景
indexOf ✅ 是 ⭐⭐⭐⭐⭐ 快速查找,不需要布尔返回值
contains ✅ 是 ⭐⭐⭐⭐ 简洁直观,适合简单判断
StringUtils.containsIgnoreCase ❌ 否 ⭐⭐⭐ 忽略大小写匹配
Pattern.find 可配置 ⭐⭐ 复杂正则匹配

✅建议:

  • 如果只是简单查找,优先使用 indexOfcontains
  • 如果需要忽略大小写,可以用 StringUtils.containsIgnoreCase
  • 如果需要正则表达式匹配,才使用 Pattern

所有示例代码均可在 GitHub 上找到。


原始标题:Check If a String Contains a Substring