问题
有个朋友跟我说去面试的时候碰到面试官被问倒。
问:怎样判断一个字符串在一个数组中?
答: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())为最佳选择