php 判断字符串首字母,PHP 里实现『是否由某字符串开头』最快的方式

最近在做开源项目 php-m3u8 的时候,为了提高解析 m3u8 文件内容的速度,需要实现『字符串是否是另外某个字符串的开头』的需求。不过在 PHP 里实现这个需求可以实现的方法实在是太多了,所以我这里做了一个测试,列出各种实现方式的速度数据,以供参考。

// 首先是用于测试的函数。

function benchmark($callback)

{

$start = microtime(true);

$callback();

return microtime(true) - $start;

}

$test = '#EXT-X-VERSION:3.0'; // 字符串,字符串越长测试效果越好

$patt = 'xx'; // 测试是否是『起始于』,php-m3u8 的 m3u8 内容匹配大部分情况是不能匹配的,所以故意写不包含的情况

$rpatt = sprintf('/^%s/', $patt); // 给正则用的

$len = strlen($patt); // 预先算好长度,后面会用到

$iterCnt = 5555555; // 测试循环的次数

// strstr 使用第三个参数是 true 的情况

echo "strstr-true:\t", benchmark(function() use ($test, $patt, $rpatt, $iterCnt, $len) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r = '' === strstr($test, $patt, true);

}

}), PHP_EOL;

// strstr 第三个参数是 false 的情况

echo "strstr-false:\t", benchmark(function() use ($test, $patt, $rpatt, $iterCnt, $len) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r = $test === strstr($test, $patt);

}

}), PHP_EOL;

// strpos

echo "strpos:\t\t", benchmark(function() use ($test, $patt, $rpatt, $iterCnt, $len) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r = 0 === strpos($test, $patt);

}

}), PHP_EOL;

// strrpos

echo "strrpos:\t", benchmark(function() use ($test, $patt, $rpatt, $iterCnt, $len) {

$len2 = strlen($test);

for ($i = 0; $i < $iterCnt; ++$i) {

$r3 = false !== strrpos($test, $patt, -$len2);

}

}), PHP_EOL;

// substr

echo "substr:\t\t", benchmark(function() use ($test, $patt, $rpatt, $iterCnt, $len) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r1 = substr($test, 0, $len) === $patt;

}

}), PHP_EOL;

// 正则

echo "preg_match:\t", benchmark(function() use ($test, $patt, $rpatt, $iterCnt, $len) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r3 = preg_match($rpatt, $test);

}

}), PHP_EOL;

// strncmp

echo "strncmp:\t", benchmark(function() use ($test, $patt, $iterCnt, $len) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r3 = strncmp($patt, $test, $len);

}

}), PHP_EOL;

// 最后测试一下自己写的函数

function startsWith($str, $prefix)

{

for ($i = 0; $i < strlen($prefix); ++$i) {

if ($prefix[$i] !== $str[$i]) {

return false;

}

}

return true;

}

echo "custom:\t\t", benchmark(function() use ($test, $patt, $iterCnt) {

for ($i = 0; $i < $iterCnt; ++$i) {

$r3 = startsWith($test, $patt);

}

}), PHP_EOL;

测试结果大家可以自行测试,总之结果是令我感到意外的。我觉得这个需求用 strncmp 来做,理论上应该是最快的,结果 strncmp 居然输给了 strpos,而 substr 和 strstr 也没 strpos 快也是挺意外……正则是 PHP 内置函数实现 startsWith 的最后一名,所以从性能考虑如果可以用别的方式代替正则,就尽量别用正则。

最后自己写的函数…… 实在惨不忍睹,所以能用 PHP 内置的函数就请尽量用吧。

所消耗时间排名:strpos ~= strrpos < substr < strstr < strncmp < preg_match < 自己实现的函数。

2017-11-27 补充:如果被测试的字符串长度非常的长,比如 2KB 以上,那么测试的情况又有点不一样了,大家可以修改上面的代码自己在机器上跑一下。总之我这边,如果是最坏情况,也就是被测试字符串根本就不包含测试字符串,strpos 的速度会变得非常的慢,而且慢到连跟自己写的函数都不是一个数量级的(之前我就怀疑过这种情况理论上来说 strpos 会慢,但当时被测试的字符串可能还是不够长,感觉 strpos 表现还是可以……)。所以不管从理论上还是实践得出的结论,在被测试字符串是超长字符串并且在最坏状况下所耗时:strrpos < substr < strncmp < preg_match < custom < (从这开始已经不属于一个数量级)strpos < strstr

当然 strrpos 之所以还那么快肯定也得益于他第三个参数的帮助。

2017-12-15 补充:今天试验了 mb 系列函数(比如 mb_strpos)做了同样的测试,结果真的是非常的惊人……附上 stackoverflow 的一篇问答。

df83231bee01dd4e52438731a8a0385e.png

写作累,服务器还越来越贵

求分担,祝愿好人一生平安

天使打赏人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值