← PHP sha1_file() 函数 PHP quotemeta() 函数 →

PHP similar_text()函数深度教程:字符串相似度计算的算法边界与工程取舍

著
原创 2026-10-11 PHP 已有人查阅

1.函数定义与返回值语义

similar_text()是PHP内置的字符串相似度计算函数,依据Oliver所著《ProgrammingClassics:ImplementingtheWorld'sBestAlgorithms》中描述的算法实现。它返回两个字符串中匹配字符的数量,而非直接返回百分比。

similar_text(string $string1, string $string2, float &$percent = null): int

第三个参数通过引用传入,调用后会被写入相似度百分比。这个设计是理解该函数的第一道门槛:返回值是整数,百分比是“副产品”。

2.匹配字符数的计算逻辑

PHP手册对该算法的描述较为简洁:“匹配字符的数量是通过查找最长的第一个公共子串,然后对前缀和后缀递归执行此操作来计算的。所有找到的公共子串的长度相加。”

用示例理解这个过程:

$count = similar_text('ab', 'a', $percent);
// $count = 1,公共子串为 "a"
// $percent = 66.666...

百分比的计算公式在手册中有明确定义:匹配字符数除以两个字符串长度的平均值,再乘以100。

$percent = $count / (($len1 + $len2) / 2) * 100;

以similar_text('ab','a')为例:匹配1个字符,长度平均值为(2+1)/2=1.5,百分比为1/1.5*100≈66.67%。

这个公式揭示了一个容易被忽视的行为:百分比可以超过100%。当两个字符串长度差异较大且公共子串很长时,匹配数可能大于平均长度。这不是bug,而是公式的直接结果。

3.参数顺序敏感:一个被文档明确警告的“非对称性”

PHP手册在string1和string2参数下方专门加注:“交换string1和string2可能会产生不同的结果”。

官方示例:

$sim = similar_text('bafoobar', 'barfoo', $perc);
// 5 (71.428571428571 %)

$sim = similar_text('barfoo', 'bafoobar', $perc);
// 3 (42.857142857143 %)

同样的两个字符串,仅仅调换参数顺序,匹配字符数从5变为3,百分比从71%降到43%。

原因:算法的递归过程以“第一个字符串”为基准,逐次查找与第二个字符串的最长公共子串。基准变化后,递归的切分方式随之改变,最终累加的公共子串长度可能不同。StackOverflow上的讨论指出,这是实现层面的设计决策而非缺陷,但确实给调用者带来了认知负担。

工程建议:在需要稳定比较结果的场景中,应当固定参数顺序。如果业务逻辑中存在“参照字符串”和“被比较字符串”的区分,始终将参照字符串放在第一位。

4.复杂度O(N³):性能问题的数学根源

PHP手册在描述段落中直接标注:“该算法的复杂度是O(N**3),N是最长字符串的长度。”与之对比,levenshtein()的复杂度为O(m×n),其中m和n分别是两个字符串的长度。

O(N³)意味着当字符串长度翻倍时,计算时间增加约8倍。这个增长曲线在短字符串场景下尚可接受,但在长文本场景下会迅速变得不可用。

PHP手册用户注释中有一条实测记录:超过20000字符时,similar_text()单次调用需要3到5秒;而在10000字符以下时,耗时仅为“一瞬间”。该用户因此在其应用中加入了长度预检:字符串超过20000字符时直接跳过similar_text()计算。

个人踩坑经历:曾在一个文章推荐功能中用similar_text()做标题相似度匹配,标题长度普遍在20到40字之间,初期数据量小、响应正常。当文章量增长到数千篇、需要在候选池中做pairwise比较时,单次请求需要遍历数百次similar_text()调用,页面加载时间从200毫秒飙升到6秒以上。后来改为基于关键词交集的自定义评分函数,同样的数据量下响应回到100毫秒以内。

5.大小写与空白字符的处理边界

similar_text()区分大小写。官方用户注释明确指出:

similar_text('Hello', 'hello'); // 4,而非 5

这一行为在拼写检查、名称匹配等场景中通常需要预处理。常见的做法是在调用前统一大小写:

similar_text(strtoupper($input), strtoupper($candidate), $percent);

空白字符同理。从文件读取的字典数据往往带有换行符,直接比较会导致意外的不匹配。使用trim()或FILE_IGNORE_NEW_LINES标志是必要的预处理步骤。

6.空字符串与短字符串的边界行为

官方用户注释记录了两个边界情况:

两个空字符串:similar_text('','',$sim)返回0,$sim被设为0。

单字符串为空:similar_text('abc','',$sim)返回0,$sim为0。百分比公式中平均长度不为零,结果仍然为0%。

单字符比较:similar_text('a','a')返回1,百分比为100%。

这些边界行为是稳定的,没有特殊异常需要处理。

7.与levenshtein()的选型判断

官方文档和用户注释中反复出现的建议是:“如果性能是一个问题,你可能希望使用levenshtein()函数代替,它具有更好的复杂度O(str1×str2)。”

两者的输出语义不同,直接比较“哪个更准”没有意义:

  • similar_text()输出“匹配字符数”和“相似百分比”,数值越大越相似。

  • levenshtein()输出“编辑距离”,即从字符串A变为字符串B所需的最少插入、删除、替换次数,数值越小越相似。

StackOverflow上的一个实例展示了两者的判断分歧:

字符串对 similar_text()百分比 levenshtein()距离
'marcoblabla'vs'robblabla' 81.8% 4
'janblabla'vs'robblabla' 70% 3

levenshtein()认为第二对更相似(距离3<4),similar_text()认为第一对更相似(百分比81.8%>70%)。哪一方的判断“更对”,取决于业务场景中对“相似”的定义。

选型判断:短字符串(如单个单词、简短标题)且需要百分比结果时,similar_text()可用;长文本、批量比较、对响应时间有要求的场景,levenshtein()或自定义词袋模型是更务实的选择。

8.中文场景的局限性

百度百科及阿里云开发者社区的文章记录了一个值得注意的现象:对于几乎相同的中文句子,similar_text()的百分比可能远低于预期。示例中两个新闻标题仅差几个字,similar_text()输出约为42%,而基于最长公共子序列的自定义实现输出约为90%。

原因在于similar_text()按字符(字节)级别计算公共子串,中文的多字节编码使得字符匹配的语义不如英文直观。对于中文相似度比较,基于分词和词袋模型的方案通常更合适。

9.常见误区速查

误区一:以为百分比可以安全地跨参数顺序使用。参数交换导致百分比变化,不能将其视为对称指标。

误区二:用similar_text()做大规模数据的两两比较。O(N³)乘以数据量的平方,性能会快速劣化。StackOverflow上有用户描述过“服务器熔毁”的场景:5条数据需要25次比较,5000条数据需要2500万次比较。

误区三:忽略大小写和空白字符的预处理。未标准化的输入会导致相似度被低估。

误区四:认为返回的百分比是“编辑距离的百分比”。它的百分比基于匹配字符数与平均长度之比,语义上与编辑距离无关。

10.练习与思考

练习:编写一个函数,接收两个字符串和一个阈值(如80),先做大小写归一化和trim,再调用similar_text(),返回布尔值表示是否达到阈值。要求处理两个空字符串的情况。

思考题:在一个拼写纠错系统中,词典有50000个单词,用户每次输入一个拼错的单词,你需要从词典中找到最相似的建议。用similar_text()逐个比较所有词典单词的方案是否可行?如果不可行,有哪些替代架构?

11、延伸阅读与参考文献

PHP 官方手册—similar_text
链接:https://www.php.net/function.similar-text
说明:算法来源(Oliver1993)、O(N³)复杂度声明、参数交换示例、百分比计算公式的官方定义。

PHP 手册用户注释—性能实测与20000字符阈值
链接:https://www.php.net/manual/en/function.similar-text.php#usernotes
说明:用户实测记录显示20000字符以上单次调用耗时3至5秒,以及基于最长公共子序列的替代实现。

StackOverflow—PHP中查找最相似字符串的方法讨论
链接:https://stackoverflow.com/questions/4947698/best-way-in-php-to-find-most-similar-strings
说明:similar_text()与levenshtein()的输出差异实例,以及基于词袋模型的轻量级替代方案讨论。

Stack Overflow—similar_text的算法复杂度与参数交换
链接:https://stackoverflow.com/questions/14226764/how-does-similar-text-work
说明:参数交换导致结果差异的讨论,以及O(N³)与O(m×n)复杂度的对比。

← PHP sha1_file()函数详解:文件哈希计算、性能优化与SHA-1安全边界 PHP quotemeta()函数详解:元字符转义边界、POSIX遗产与现代替代方案 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号