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 的 Pattern
和 Matcher
类配合正则表达式。
示例:
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 |
可配置 | ⭐⭐ | 复杂正则匹配 |
✅建议:
- 如果只是简单查找,优先使用
indexOf
或contains
。 - 如果需要忽略大小写,可以用
StringUtils.containsIgnoreCase
。 - 如果需要正则表达式匹配,才使用
Pattern
。
所有示例代码均可在 GitHub 上找到。