为什么java String.contains 没有使用类似KMP字符串匹配算法进行优化?

阅读JDK源码无意间发现String的contains函数进行字串匹配的时候使用的是简单的O(n*m)(源字符串长度n,要匹配字串长度m)复杂度的算法…
关注者
133
被浏览
42,892

5 个回答

这要找到最正确的答案恐怕得去问问Java的元老们才行⋯任何其他人的“分析”都只能是猜测。

毕竟这块代码(String.indexOf(String))的实现从Sun JDK 1.0.2开始到现在的Oracle JDK8u一直都没怎么变过;只有少量细节变了点,算法思路完全没变。

从以下两个OpenJDK邮件列表的讨论串看,最大的可能是“还没优化到这里”和“常用场景下这个实现已经很快,外加这个实现不需要额外空间开销”。

Inefficient code of String.indexOf(String)String.indexOf optimization

结论是:欢迎提交改进的patch;“你行你上”系列。

注意:新的“优化”实现必须要在“常用场景”上能比当前实现有显著性能提升,而且额外空间开销不会很大,才有可能被接受。KMP在“常用场景”上未必能做到这点:初始化的时间开销、额外的空间开销都会成为绊脚石。

所谓“常用场景”,Java里大多数String的长度其实并不长。有兴趣的话可以跑一个您的应用场景里的典型应用统计一下长度。

另外题主肯定没有关注的是,虽然OpenJDK的String.indexOf()的源码写成那样,在许多JVM里这个方法都有内部的特定优化——JVM直接无视原本Java版的实现,而在内部重新实现一遍。

Oracle JDK / OpenJDK的HotSpot VM就会做这样的优化,例如在有SSE4.2指令的x86上会用SSE4.2的STTNI系指令pcmpestri来实现String.indexOf()。我在这里放过一个笔记:

gist.github.com/rednaxe

我相信IBM J9 VM的Testarossa JIT编译器也有类似的优化。

这样任凭别人怎么修改Java版的实现,等到被JIT编译之后都无所谓了⋯

所以如果题主有兴趣提交patch给OpenJDK去改进String.indexOf()的性能,请记住要用参数

-XX:DisableIntrinsic=_indexOf

来测试。不然无论您怎么改Java代码最后跑的搞不好都是一样的东西。

KMP的O(n)在实际应用并不那么理想,

平均性能大都比不上Boyer-Moore 或 简化的 horspool算法,

facebook 的folly库就采用Boyer-Moore算法,

不过与KMP类似的Aho-Corasick在压缩空间后还是很可观的。

Snort初期采用的是Aho-Corasick,但现在好像改用wu-manber了,太久没关注了。

若想深入了解,可以去查阅这本书<< 柔性字符串匹配 >>