一、前言
在面试或者算法学习中,我们多少会接触到一些字符串匹配类型的题目,那么在java中,字符串匹配是怎么实现的呢?
二、Java中的字符串匹配方法
java.lang.String 中用来判断字符串匹配的底层代码如下, 返回值不是" -1 ",即表示目标字符串在源字符串中存在。代码分析已逐行给出注释。
/**
* Code shared by String and StringBuffer to do searches. The
* source is the character array being searched, and the target
* is the string being searched for.
*
* 以下注释的默认值意思是以String.contains场景下的取值说明
* @param source 用于搜索的源字符串
* @param sourceOffset 从源字符串的第几位开始搜索, 默认0
* @param sourceCount 源字符串的搜索长度,默认总长度
* @param target 目标字符串
* @param targetOffset 从目标字符串的第几位开始查询,默认0
* @param targetCount 目标字符串的搜索长度,默认总长度
* @param fromIndex 搜索的起始索引位置,默认0
*
*/
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
// 即判断用于搜索的源字符串长度是否为0,
// 如果源字符串长度为0,目标字符串长度也为0,返回源字符串长度
// 如果源字符串长度为0,目标字符串长度不为0,返回 -1 (找不到匹配结果)
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
// 搜索的起始索引位置,对非法值做约束
if (fromIndex < 0) {
fromIndex = 0;
}
// 目标字符串长度为0, 直接返回搜索的起始索引位置
if (targetCount == 0) {
return fromIndex;
}
// 目标字符串的首个字符
char first = target[targetOffset];
// 设置最大搜索位置,若目标字符串和源字符串长度相近时, 这个值很小
int max = sourceOffset + (sourceCount - targetCount);
// 遍历源字符串
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
// while 遍历,找到下一个首字符能匹配上的位置
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
// 首字符匹配上,确认后续字符串是否能匹配上
if (i <= max) {
// 从第2个字符开始检查到目标字符串长度的最后一个字符
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) {
/* Found whole string. */
// 返回首个完成匹配的子串起始位置
return i - sourceOffset;
}
}
}
// 没有找到匹配的字符串
return -1;
}
从上述代码可以看出, String类对字符串contains的实现,是稍有优化后的暴力解法,时间复杂度O(n2),空间复杂度O(1)。
也许会有些诧异,为什么这么常用的一个功能,jdk选择了使用暴力法实现。看到网上之前已经有过相关讨论:为什么java String.contains 没有使用类似KMP字符串匹配算法进行优化?, 更权威的解释参照文末更新部分
实际上,竟然已经这么久都没有人对String中的contains进行优化,根据帕累托法则,我们可以猜想,大部分的使用场景通过暴力求解,效率上已经足够了,而选择其他的优化方法,对于常用的场景来说,优化程度可能并不高。
不管真实原因是什么,了解了contains的底层实现,后续我们在方案设计、编码、评审上还是要多一个心眼,适当的根据场景选用更合适的字符串匹配算法。
值得一提的是,String 的 startsWith 、 endsWith、compareTo 都可以当做是字符串匹配方法的一个特例,但是JDK中,并没有使用 indexOf方法来实现。而是单独写方法来实现。
更新:针对上述为什么JDK使用了暴力方式做字符串匹配,在《柔性字符串匹配》一书中开篇就提到以下观点:“算法的思想越简单,实际应用的效果越好”,原文如下



3160

被折叠的 条评论
为什么被折叠?



