strtr

转换字符串中特定的字符,但是这个函数使用的方式多种。

echo strtr('hello world', 'hw', 'ab'); // 第一种 aello borld
echo strtr('hello world', 'hw', 'a'); // 第二种 aello world
echo strtr('hello world', ['hello' => 'hi']); // 第三种 hi world
echo strtr('hello world', ['he' => 'th', 'hello' => 'hi']); // 第四种 hi world

时间复杂度

O(n),最差是O(n*m)

源码

以下根据每种情况逐一分析源码。
第一种、第二种,也是最常用的,但第二种,只有’h’转换成’a’,’w’没有被处理。这种方式的替换,会以短的一方为准。如果from和to其中一个是空串,会直接返回原字符串。

RETURN_STR(php_strtr_ex(str,
Z_STRVAL_P(from),
to,
MIN(Z_STRLEN_P(from), to_len)));
// 从源码MIN(Z_STRLEN_P(from), to_len))可以看出来,以from、to两个字符串短的为准,剩余的会被忽略掉,所以可以解释第二种情况'w'被忽略掉
// 同理,以下to中的'b'也会被忽略掉
strtr('hello world', 'h', 'ab'); // aello world

接着,我们主要看下php_strtr_ex方法,是怎么实现字符转换。源码是使用hash表实现,hash表把from的每个字符,一一对应为to的相应位置的字符。

static zend_string *php_strtr_ex(zend_string *str, char *str_from, char *str_to, size_t trlen)
{
// trlen的值就是MIN(Z_STRLEN_P(from), to_len))
// 先构建一个hash表,用php伪代码来解释第一种情况构建好的hash表
// array('g'=>'g','h'=>'a','i'=>'i','w'=>'b')
unsigned char xlat[256], j = 0;
do { xlat[j] = j; } while (++j != 256);
for (i = 0; i < trlen; i++) {
xlat[(size_t)(unsigned char) str_from[i]] = str_to[i];
}
// 接着遍历字符串,从hash表中找到转换的字符
for (i = 0; i < ZSTR_LEN(str); i++) {
if (ZSTR_VAL(str)[i] != xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]) {
new_str = zend_string_alloc(ZSTR_LEN(str), 0);
memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i);
// 从hash表中找到转换的字符
ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]];
break;
}
}
for (;i < ZSTR_LEN(str); i++) {
// 从hash表中找到转换的字符
ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]];
}
}

第三种、第四种from是个数组,如果from是数组,情况就不是一对一的字符转换,是字符串对字符串的转换了,把key整个字符串转换成value字符串。

第三种,from数组只有一对键值对,实现思路是,根据kmp算法在主串中搜索key(被替换的字符串)的位置,如果找到,就使用value替换掉。kmp本身的效率是O(n),所以如果字符串内进行了m次替换,这种情况下strtr效率会是O(n*m)

// 搜索被替换的字符串的所有位置
e = s = ZSTR_VAL(new_str);
end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
// php_memnstr搜索 被替换的字符串 的所有位置,并替换掉
for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
memcpy(e, p, r - p);
e += r - p;
memcpy(e, str, str_len);
e += str_len;
(*replace_count)++;
}

第四种,通过数组替换多个字符串,这种是各种情况效率最差的

// 先构造所有 被替换的字符串
ZEND_HASH_FOREACH_STR_KEY(pats, str_key) {
len = ZSTR_LEN(str_key);
// 计算所有 被替换的字符串 最长和最短值
if (len > maxlen) {
maxlen = len;
}
if (len < minlen) {
minlen = len;
}
// 记录每个key长度值的hash值
num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong));
// 记录每个key首字符的hash值
bitset[((unsigned char)ZSTR_VAL(str_key)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(str_key)[0]) % sizeof(zend_ulong)); } ZEND_HASH_FOREACH_END();
// 辅助两个hash表,替换的字符串
old_pos = pos = 0;
while (pos <= slen - minlen) {
key = str + pos;
// 如果从首字符的hash表匹配到,表示以key[0]字符开头的有可能是被替换的字符串
if (bitset[((unsigned char)key[0]) / sizeof(zend_ulong)] & (Z_UL(1) << (((unsigned char)key[0]) % sizeof(zend_ulong)))) {
len = maxlen;
if (len > slen - pos) {
len = slen - pos;
}
// key从maxlen循环到minlen,所以,第四种'hello'和'he',最先匹配到hello
while (len >= minlen) {
// 如果从长度hash表里面匹配到被替换的字符串里可能的长度,就从from数组里面找到替换的键值对zend_hash_str_find
if ((num_bitset[len / sizeof(zend_ulong)] & (Z_UL(1) << (len % sizeof(zend_ulong))))) {
entry = zend_hash_str_find(pats, key, len);
if (entry != NULL) {
zend_string *s = zval_get_string(entry);
smart_str_appendl(&result, str + old_pos, pos - old_pos);
smart_str_append(&result, s);
old_pos = pos + len;
pos = old_pos - 1;
zend_string_release(s);
break;
}
}
len--;
}
}
pos++;
}

这种情况有点复杂,下面的php伪代码翻译一下以上的C语言代码

$bitset = array_fill(0, 255, 0); // 首字符的hash表
$num_bitset = array_fill(0, 255, 0); // key长度值的hash值
$min_len = PHP_INT_MAX;
$max_len = 0;
$len = 0;
// echo strtr('hello world', ['he' => 'th', 'hello' => 'hi']);
$pats = ['he', 'hello'];
foreach($pats as $v){
$len = strlen($v);
if($len > $max_len) {
$max_len = $len;
}
if($len < $min_len) {
$min_len = $len;
}
$num_bitset[intdiv($len,8)] |= 1 << ($len%8);
$bitset[intdiv(ord($v[0]),8)] |= 1 << (ord($v[0])%8);
}
// print_r(array_unique($num_bitset));
// print_r(array_unique($bitset));
// 例如我们匹配hello,首字符是h,长度5
// 以下两行就是以上C语言的while循环里面两个if判断
echo $bitset[intdiv(ord('h'),8)] & 1 << (ord('h')%8),PHP_EOL;
echo $num_bitset[intdiv(5,8)] & 1 << (5%8),PHP_EOL;

本文链接:JC博客--PHP源码阅读strtr

原创文章,转载请注明来源

 

PHP源码阅读strtr的更多相关文章

  1. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  2. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  3. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  4. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  5. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  6. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  7. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

  8. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  9. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

随机推荐

  1. 开涛spring3(9.2) - Spring的事务 之 9.2 数据库事务概述

    9.2.1  概述 Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如Hibernate)通过实现策略接口 PlatformTransactionManager,从而能支持 ...

  2. SmartCoder每日站立会议07

    站立会议内容     基本框架已出,首页地图功能还没有接入完全. 1.站立会议照片: 2.任务展板 3.燃尽图

  3. windows下Python 3.x图形图像处理库PIL的安装

    图像处理是一门应用非常广的技术,而拥有非常丰富第三方扩展库的 Python 当然不会错过这一门盛宴.PIL (Python Imaging Library)是 Python 中最常用的图像处理库,目前 ...

  4. java编程之泛型

    java泛型实现了"参数化类型"的概念,所谓"参数化类型"是指将操作的数据类型指定为一个参数,这点在容器中用的最多,例如:List<String> ...

  5. 发布支持多线程的PowerShell模块 —— MultiThreadTaskRunner

    应用场景 多线程在需要批量处理一些任务的时候相当有用,也更加有利于充分利用现有计算机的能力.所有主流的开发语言都支持多线程. 默认情况下,PowerShell作为一个脚本语言,是不支持多线程操作的,虽 ...

  6. 学习笔记TF018:词向量、维基百科语料库训练词向量模型

    词向量嵌入需要高效率处理大规模文本语料库.word2vec.简单方式,词送入独热编码(one-hot encoding)学习系统,长度为词汇表长度的向量,词语对应位置元素为1,其余元素为0.向量维数很 ...

  7. Java IO流之【缓冲流和文件流复制文件对比】

    与文件流相比,缓冲流复制文件更快 代码: package Homework; import java.io.BufferedOutputStream; import java.io.File; imp ...

  8. Java异常类(Throwable)

    一.异常类体系 二.异常类由来与定义 [异常类的由来]:Java把程序在运行时出现的各种不正常情况也看成了对象, 提取属性和行为进行描述,比如异常名称,异常信息,异常发生位置,从而形成了各种异常类 [ ...

  9. NLTK学习笔记(三):NLTK的一些工具

    主要总结一下简单的工具:条件频率分布.正则表达式.词干提取器和归并器. 条件分布频率 <自然语言学习>很多地方都用到了条件分布频率,nltk提供了两种常用的接口:FreqDist 和 Co ...

  10. */美女镇楼/*>>>---PHP中的OOP-->面对过程与面对对象基础概念与内容--(封装、继承、多态)

      前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的重要成员OOP 1  面向过程&面向对象 1.专注于解决一个问题的过程.面向过程的最大特点,是由一个一个的函数去解决处理这 ...