在Java编程中,判断一个字符串是否包含指定子串是日常开发中非常常见的需求,Java的String类提供了contains和indexOf两个方法来实现这类判断,很多开发者对两者的区别和底层实现并不清晰,本文将详细展开分析。

两个方法的基本用法
首先我们来看两个方法的基础使用方式,两者的调用形式都比较简单,先给出基础的代码示例。
public class StringContainsDemo {
public static void main(String[] args) {
String mainStr = "hello world java";
String subStr = "world";
// 使用contains方法判断
boolean containsResult = mainStr.contains(subStr);
System.out.println("contains判断结果:" + containsResult);
// 使用indexOf方法判断
int indexOfResult = mainStr.indexOf(subStr);
boolean indexOfCheck = indexOfResult != -1;
System.out.println("indexOf判断结果:" + indexOfCheck);
}
}
从上面的代码可以看到,contains方法直接返回布尔值,而indexOf方法返回子串在主串中首次出现的索引位置,当返回-1时表示不存在子串,因此需要通过判断返回值是否不等于-1来得到是否包含的布尔结果。
底层实现源码对比
contains方法的源码
我们先查看String类中contains方法的实现,源码如下:
public boolean contains(CharSequence s) {
// 调用indexOf方法,判断返回值是否大于等于0
return indexOf(s.toString()) >= 0;
}
可以看到contains方法本身没有额外的复杂逻辑,它本质上是直接调用了indexOf方法,然后判断indexOf的返回值是否大于等于0,如果大于等于0就返回true,否则返回false。
indexOf方法的源码
接下来看indexOf方法的实现,String类中有多个重载的indexOf方法,最常用的是接收String参数的版本,其核心逻辑如下:
public int indexOf(String str) {
return indexOf(str, 0);
}
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
最终会调用一个静态的indexOf方法,这个方法是实现子串查找的核心,底层采用的是经典的朴素字符串匹配算法,源码简化后逻辑如下:
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
// 先找到第一个匹配的字符
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
// 找到第一个匹配字符后,继续匹配后续字符
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++);
if (j == end) {
// 匹配成功,返回索引
return i - sourceOffset;
}
}
}
return -1;
}
从源码可以看出,indexOf的底层实现是从主串的指定位置开始,逐个字符匹配子串,时间复杂度为O(n*m),其中n是主串长度,m是子串长度。
两者的核心差异
| 对比维度 | contains方法 | indexOf方法 |
|---|---|---|
| 返回值类型 | 布尔值,直接返回是否包含子串 | 整型,返回子串首次出现的索引,不存在返回-1 |
| 功能范围 | 仅能判断是否存在子串 | 不仅能判断是否存在,还能获取子串的位置,也支持从指定索引开始查找 |
| 底层实现 | 直接调用indexOf方法,无额外逻辑 | 实现朴素字符串匹配算法,是子串查找的核心实现 |
| 性能表现 | 和indexOf判断是否存在时的性能几乎一致,仅多了一层方法调用 | 单独使用时如果只需要判断是否存在,会多一次返回值比较的开销(可忽略) |
实际开发中的选择建议
根据两者的特性,我们可以按照以下场景选择:
- 如果只需要判断字符串是否包含子串,不需要知道子串的位置,优先使用
contains方法,代码可读性更高,语义更明确。 - 如果需要获取子串在主串中的位置,或者需要从指定索引开始查找子串,只能使用
indexOf方法。 - 如果既需要判断是否存在,又需要后续使用子串的位置,那么直接使用
indexOf方法保存返回值,避免重复调用查找逻辑。
需要注意,两者的底层匹配算法都是朴素匹配,在处理超长字符串且对性能要求极高的场景下,可能需要考虑使用更高效的字符串匹配算法,比如KMP算法,但普通业务场景下两者的性能完全足够使用。