在PHP开发中,处理字符串匹配时经常会遇到需要判断某个字符是否在字符串中重复出现两次及以上的需求,而且这种重复可能是非连续的,比如字符串abcad中字符a就是非连续重复了两次。很多开发者一开始会尝试用类似(.)1+的正则去匹配,但这种写法只能匹配连续重复的字符,无法覆盖非连续的情况,因此需要更合适的正则写法来实现需求。

核心正则语法解析
要实现任意字符重复两次及以上(含非连续)的匹配,核心需要用到正则的两个特性:正向肯定预查和反向引用。首先用(.)捕获第一个出现的任意字符,然后通过正向肯定预查(?=.*1)来判断后续位置是否存在和前面捕获的字符相同的字符,这里.*表示中间可以有任意数量的任意字符,刚好覆盖非连续的情况。
完整的匹配模式可以写成/(.).*(?=.*1)/s,其中s是修正符,作用是让.可以匹配包括换行符在内的所有字符,避免出现换行导致匹配失败的情况。
PHP代码实现示例
下面给出具体的PHP实现代码,包含匹配判断和获取重复字符两个常用场景:
<?php
/**
* 判断字符串中是否存在任意字符重复出现两次及以上(含非连续)
* @param string $str 待检测字符串
* @return bool 存在返回true,否则返回false
*/
function hasRepeatChar($str) {
// 正则模式:捕获任意字符,之后任意内容,再之后存在和捕获字符相同的字符
$pattern = '/(.).*(?=.*1)/s';
return preg_match($pattern, $str) > 0;
}
/**
* 获取字符串中所有重复出现两次及以上的字符(含非连续)
* @param string $str 待处理字符串
* @return array 重复字符组成的数组
*/
function getRepeatChars($str) {
$pattern = '/(.).*(?=.*1)/s';
$matches = [];
// 全局匹配所有符合的捕获字符
preg_match_all($pattern, $str, $matches);
// 去重后返回结果
return array_unique($matches[1]);
}
// 测试示例
$testStr1 = 'abcad';
$testStr2 = '123456';
$testStr3 = 'abcaefga';
var_dump(hasRepeatChar($testStr1)); // 输出bool(true)
var_dump(hasRepeatChar($testStr2)); // 输出bool(false)
var_dump(getRepeatChars($testStr3)); // 输出array(2) { [0]=>string(1) "a" [2]=>string(1) "b" }
?>
注意事项说明
- 如果不需要匹配换行符,可以去掉正则末尾的
s修正符,此时.只会匹配除换行符外的任意字符。 - 上述正则匹配的是任意单个字符的重复,如果需要匹配特定字符集合的重复,可以把
(.)中的.替换成对应的字符范围,比如([a-z])表示只匹配小写字母的重复。 - 如果字符串中存在多个不同字符重复的情况,
preg_match_all会捕获所有符合条件的重复字符,再通过array_unique去重就能得到所有重复的字符列表。
常见问题解答
为什么不能用(.){2,}来匹配?
(.){2,}的含义是捕获一个字符,然后这个字符连续出现两次及以上,只能匹配连续重复的字符,比如aa、bbb,无法匹配abcad中a这种非连续重复的情况,因此不符合需求。
如果只需要判断是否存在重复,不用获取具体字符用哪个函数更合适?
用preg_match即可,它只要匹配到第一个符合条件的结果就会返回,性能比preg_match_all更高,适合只需要判断存在性的场景。