将一个字符串插入到另外一个字符串中间,是php中常常出现的一个操作,具体的实现方案也有替换和直接插入两种,涉及到的相关函数也不少,下面就是我在一个实际工作当中处理字符串插入的效率的调研。
场景:
在一个 1MB左右的文本文件(content)中的 “hello” 前插入一个10个字节大小的字符串(string)。
采取的方案:(机器性能不同,用相对的比较方式对比性能)
编号 | 方式 | 性能(此处是相对值,仅表示各函数的相对速度大小) | 备注 |
---|---|---|---|
A | 【替换】str_replace(‘#hello#’,$string,$content) | 3 | 备注 |
B | 【替换】strtr($content,array(”=>$script)); | 42 | 备注 |
C | 【正则替换】preg_replace(‘##hello##’,$script,$content); | 4.4 | 备注 |
D | 【Substr_replace +strops 插入】substr_replace($content,$script,strpos($content,”),0) | 1 | 备注 |
A 方案 str_replace() 函数
函数签名:
mixed str_replace ( mixed
$search
, mixed$replace
, mixed$subject
[, int&$count
] )参数
如果 search 和 replace 为数组,那么 str_replace() 将对 subject 做二者的映射替换。如果 replace 的值的个数少于 search 的个数,多余的替换将使用空字符串来进行。如果 search 是一个数组而 replace 是一个字符串,那么 search 中每个元素的替换将始终使用这个字符串。该转换不会改变大小写。如果 search 和 replace 都是数组,它们的值将会被依次处理。
search
查找的目标值,也就是 needle。一个数组可以指定多个目标。
replace
search 的替换值。一个数组可以被用来指定多重替换。
subject
执行替换的数组或者字符串。也就是 haystack。如果 subject 是一个数组,替换操作将遍历整个 subject,返回值也将是一个数组。
count
如果被指定,它的值将被设置为替换发生的次数。
返回值 ¶
该函数返回替换后的数组或者字符串。
输入的类型可以是字符串或者数组,先对字符串进行分析
情况1.输入的 search 和 replace 均为字符串
- 替换的过程首先要查找 search ,在一个字符串中查找另外一个字符串,就变成了经典的 ‘find needls in haystack’ 问题了。
在源码中,调用情况是 str_replace->php_str_to_str_ex[字符串替换]->zend_memstr php_str_to_str_ex在 ext/standerd/string.c 的php_str_to_str_ex 顾名思义,他就是主要负责处理字符串替换,而 zend_memstr 主要是查找 needles
zend_memstr 源码:
1 | static inline char * |
在使用 zend_memstr 查找到 search 的内存地址之后,php_str_to_str_ex 就会使用memcpy ,用 to 来替换 from。
【值得注意的是,在php_str_to_str_ex 中,会一直查找 subject 中的 search ,知道把他们全部替换为 replace】也就是说,这个过程在发生一次之后不会退出,直到循环,将所有 content 里的 to 替换成 from 才会退出。
当 str_replace 输入为数组时,使用的算法和 输入为 string 是一样的,只不过相当于循环的处理输入的数组,将他们单个取出,像对待字符串一样去处理。
方案B strtr() 函数
说明 ¶
string strtr ( string
$str
, string$from
, string$to
)string strtr ( string
$str
, array$replace_pairs
)该函数返回
str
的一个副本,并将在from
中指定的字符转换为to
中相应的字符。 比如, $from[$n]中每次的出现都会被替换为 $to[$n],其中 $n 是两个参数都有效的位移(offset)。如果
from
与to
长度不相等,那么多余的字符部分将被忽略。str
的长度将会和返回的值一样。参数 ¶
str
待转换的字符串
from
字符串中与将要被转换的目的字符
to
相对应的源字符
to
字符串中与将要被转换的字符
from
相对应的目的字符
replace_pairs
参数
replace_pairs
可以用来取代to
和from
参数,因为它是以 array(‘from’ => ‘to’, …) 格式出现的数组。返回值 ¶
返回转换后的字符串。
如果
replace_pairs
中包含一个空字符串(“”)键,那么将返回 FALSE。 If thestr
is not a scalar then it is not typecasted into a string, instead a warning is raised and NULLis returned.
strtr 有两种输入情况,源码中也是将它分成了两个子过程来处理:
输入为字符串:
1 | PHPAPI char *php_strtr(char *str, int len, char *str_from, char *str_to, int trlen) |
输入为数组:
1 | /* {{{ php_strtr_array |
从 strtr 的执行过程来看,strtr 也是需要遍历整个 subject 来替换 to -> from,这个也是它在我们的场景中他的效率比较慢的原因。
方案C 正则替换
正则替换涉及到正则解析引擎,这个应该也是需要遍历 subject 来替换 to -> from。
方案D substr_replace +strops 插入
substr_replace 函数签名:
mixed substr_replace ( mixed
$string
, mixed$replacement
, mixed$start
[, mixed$length
] )substr_replace() 在字符串
string
的副本中将由start
和可选的length
参数限定的子字符串使用replacement
进行替换。
strpos 函数签名:
说明 ¶
mixed strpos ( string
$haystack
, mixed$needle
[, int$offset
= 0 ] )返回
needle
在haystack
中首次出现的数字位置。
使用 strpos 定位目标串位置,然后 substr_replace 快速替换。strpos 使用上面提到的 zend_memstr 查找 needle,然后 substr_replace 替换,替换的核心操作就是涉及到内存的分配。这个方法,只是会在目标串中查找一次needle,相比之前的方法,减少了目标串的遍历,由于我们的目标串较大,所以这个方法在相对的比较中,占据了较大的优势.
总结:
在网上搜索字符串替换方案时,会有各种五花八门的解释和分析,但是都缺少原理上的分析和具体场景。在这次调研过程中,我也发现php在一个问题的解决上,有较多的解决办法,看似相同,却又有区别,这个就要对源码较清晰的了解方法的执行过程,才能写出较好性能的程序。