问题

有个朋友跟我说去面试的时候碰到面试官被问倒。
问:怎样判断一个字符串在一个数组中?
答:in_array函数
问:是否有优化的空间呢?
答:...这还需要优化吗?


分析

先来看看in_array的官方解释

in_array — 检查数组中是否存在某个值
bool in_array  ( mixed  $needle  , array $haystack  [, bool $strict  = FALSE ] )
在 haystack 中搜索 needle,如果没有设置 strict 则使用宽松的比较

关键在第三个参数,如果第三个参数 strict 的值为 TRUE  则 in_array()  函数还会检查 needle 的类型是否和 haystack 中的相同。 按我自己的理解,设置会后,首先会检查两个要比较的变量的类型,如果类型不同,直接就返回false,不会比较值。比如下面这个例子:

$q = "1";
$num = array("1", "abc", "ddd");
var_dump(in_array((int) $q, $num, true)); 

返回的值为false,因为 (int) $q之后,类型为 integer,开启严格模式之后,由于数组内都为string类型,直接返回 false,看起来确实比不开启严格模式搜索要快一些。


疑问

那么,除了设置第三个参数外,还有没有其他的优化空间呢?这个时候想到了函数array_flip,下面我们来看看官方的解释

array_flip — 交换数组中的键和值
array_flip()  返回一个反转后的 array ,例如 trans 中的键名变成了值,而 trans 中的值成了键名。
注意 trans 中的值需要能够作为合法的键名,例如需要是 integer  或者 string 。如果值的类型不对将发出一个警告,并且有问题的键/值对将不会反转。
如果同一个值出现了多次,则最后一个键名将作为它的值,所有其它的都丢失了。

也就是,反转之后,如果有相同的value,数组的长度 < 反转前的数组长度,这对于循环查找将带来略微的提升。反转之后,可以通过 isset($haystack[$needle])来判断是否存在,当然也可以通过array_key_exists来判断,但是isset是内建运算符,array_key_exists是php内置函数,isset要快一些。具体可以参考:PHP 函数实现原理及性能分析


实验

那 isset(array_flip($needle)) 究竟能提高多少性能呢,下面我测试了一组数据,基础数据位5位大小写字母+数字的随机字符串

1、测试基数 $haystack 长度为10,$needle 长度为1

in_array() isset(array_flip())
0.01907ms 0.00906ms
0.02003ms 0.00906ms
0.01907ms 0.00906ms
0.01884ms 0.00882ms
0.01907ms 0.01311ms
0.02003ms 0.00906ms
0.02313ms 0.01407ms
0.02408ms 0.01407ms
0.01788ms 0.00906ms
0.01788ms 0.00906ms

平均值 0.1991ms 0.1044ms
优化后性能提升约为1倍

2、测试基数 $haystack 长度为100,$needle 长度为1

in_array() isset(array_flip())
0.02599ms 0.02193ms
0.03409ms 0.03695ms
0.02503ms 0.02217ms
0.02599ms 0.02098ms
0.02599ms 0.02098ms
0.02885ms 0.03195ms
0.02503ms 0.02193ms
0.02503ms 0.02217ms
0.02623ms 0.02193ms
0.02503ms 0.02193ms

平均值 0.2673ms 0.2429ms
两者性能几乎差不多

3、测试基数 $haystack 长度为1000,$needle 长度为1

in_array() isset(array_flip())
0.09608ms 0.17810ms
0.11301ms 0.25916ms
0.08988ms 0.17190ms
0.10681ms 0.19884ms
0.11802ms 0.26417ms
0.09108ms 0.16499ms
0.09513ms 0.16904ms
0.09584ms 0.20289ms
0.09298ms 0.16499ms
0.11396ms 0.27800ms

平均值 0.10128ms 0.20521ms
后者居然比in_array慢一倍

4、测试基数 $haystack 长度为10,$needle 长度为5

in_array() isset(array_flip())
0.02217ms 0.01001ms
0.02503ms 0.01097ms
0.02289ms 0.00906ms
0.02193ms 0.00882ms
0.02694ms 0.01097ms
0.02217ms 0.00906ms
0.02289ms 0.01001ms
0.02122ms 0.00906ms
0.02098ms 0.01001ms
0.02098ms 0.01311ms

平均值 0.02272ms 0.01011ms
优化后性能提升约为1倍多

5、测试基数 $haystack 长度为100,$needle 长度为5

in_array() isset(array_flip())
0.04506ms 0.02599ms
0.04005ms 0.02098ms
0.04196ms 0.02217ms
0.04101ms 0.02193ms
0.04315ms 0.02313ms
0.04411ms 0.02193ms
0.04005ms 0.02217ms
0.04196ms 0.02289ms
0.05579ms 0.03791ms
0.04101ms 0.02313ms

平均值 0.04342ms 0.02382ms
优化后性能提升约为1倍

6、测试基数 $haystack 长度为1000,$needle 长度为10

in_array() isset(array_flip())
0.41509ms 0.14806ms
0.40698ms 0.17810ms
0.41485ms 0.14901ms
0.41413ms 0.14806ms
0.44680ms 0.23913ms
0.40698ms 0.15306ms
0.42701ms 0.14901ms
0.42582ms 0.14997ms
0.39792ms 0.14901ms
0.41699ms 0.17309ms

平均值 0.41726ms 0.16365ms
优化后性能提升约为2.5倍

10/1 isset(array_flip()) 性能提升1倍
100/1 两者性能接近
1000/1 in_array() 胜出,性能为isset(array_flip()) 1倍
...
再往下增加基数,in_array() 性能要优于 isset(array_flip())

10/5 isset(array_flip()) 性能提升1倍
100/5 isset(array_flip()) 性能提升1倍
1000/10 isset(array_flip()) 性能提升2.5倍
1000/100 isset(array_flip()) 性能提升25倍
...
isset(array_flip()) 一直胜出,不管增加基数还是搜索的次数,性能都比in_array优越


结论

1、如果明确了数据类型,建议in_array第三个参数建议一直开启
2、如果是搜索单一字符串,基数100以内,可以选择isset(array_flip()), 基数在100以上,则选择in_array()为宜。如果选择的是搜索数组,则isset(array_flip())为最佳选择

附件

num.php (1.93 KB, 下载次数:564, 上传时间:2014-11-11 03:45)