strtr

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

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

时间复杂度

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

源码

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

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

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

  1. static zend_string *php_strtr_ex(zend_string *str, char *str_from, char *str_to, size_t trlen)
  2. {
  3. // trlen的值就是MIN(Z_STRLEN_P(from), to_len))
  4. // 先构建一个hash表,用php伪代码来解释第一种情况构建好的hash表
  5. // array('g'=>'g','h'=>'a','i'=>'i','w'=>'b')
  6. unsigned char xlat[256], j = 0;
  7. do { xlat[j] = j; } while (++j != 256);
  8. for (i = 0; i < trlen; i++) {
  9. xlat[(size_t)(unsigned char) str_from[i]] = str_to[i];
  10. }
  11. // 接着遍历字符串,从hash表中找到转换的字符
  12. for (i = 0; i < ZSTR_LEN(str); i++) {
  13. if (ZSTR_VAL(str)[i] != xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]) {
  14. new_str = zend_string_alloc(ZSTR_LEN(str), 0);
  15. memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i);
  16. // 从hash表中找到转换的字符
  17. ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]];
  18. break;
  19. }
  20. }
  21. for (;i < ZSTR_LEN(str); i++) {
  22. // 从hash表中找到转换的字符
  23. ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]];
  24. }
  25. }

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

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

  1. // 搜索被替换的字符串的所有位置
  2. e = s = ZSTR_VAL(new_str);
  3. end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
  4. // php_memnstr搜索 被替换的字符串 的所有位置,并替换掉
  5. for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
  6. memcpy(e, p, r - p);
  7. e += r - p;
  8. memcpy(e, str, str_len);
  9. e += str_len;
  10. (*replace_count)++;
  11. }

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

  1. // 先构造所有 被替换的字符串
  2. ZEND_HASH_FOREACH_STR_KEY(pats, str_key) {
  3. len = ZSTR_LEN(str_key);
  4. // 计算所有 被替换的字符串 最长和最短值
  5. if (len > maxlen) {
  6. maxlen = len;
  7. }
  8. if (len < minlen) {
  9. minlen = len;
  10. }
  11. // 记录每个key长度值的hash值
  12. num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong));
  13. // 记录每个key首字符的hash值
  14. bitset[((unsigned char)ZSTR_VAL(str_key)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(str_key)[0]) % sizeof(zend_ulong));
  15.  
  16. } ZEND_HASH_FOREACH_END();
  17. // 辅助两个hash表,替换的字符串
  18. old_pos = pos = 0;
  19. while (pos <= slen - minlen) {
  20. key = str + pos;
  21. // 如果从首字符的hash表匹配到,表示以key[0]字符开头的有可能是被替换的字符串
  22. if (bitset[((unsigned char)key[0]) / sizeof(zend_ulong)] & (Z_UL(1) << (((unsigned char)key[0]) % sizeof(zend_ulong)))) {
  23. len = maxlen;
  24. if (len > slen - pos) {
  25. len = slen - pos;
  26. }
  27. // key从maxlen循环到minlen,所以,第四种'hello'和'he',最先匹配到hello
  28. while (len >= minlen) {
  29. // 如果从长度hash表里面匹配到被替换的字符串里可能的长度,就从from数组里面找到替换的键值对zend_hash_str_find
  30. if ((num_bitset[len / sizeof(zend_ulong)] & (Z_UL(1) << (len % sizeof(zend_ulong))))) {
  31. entry = zend_hash_str_find(pats, key, len);
  32. if (entry != NULL) {
  33. zend_string *s = zval_get_string(entry);
  34. smart_str_appendl(&result, str + old_pos, pos - old_pos);
  35. smart_str_append(&result, s);
  36. old_pos = pos + len;
  37. pos = old_pos - 1;
  38. zend_string_release(s);
  39. break;
  40. }
  41. }
  42. len--;
  43. }
  44. }
  45. pos++;
  46. }

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

  1. $bitset = array_fill(0, 255, 0); // 首字符的hash表
  2. $num_bitset = array_fill(0, 255, 0); // key长度值的hash值
  3. $min_len = PHP_INT_MAX;
  4. $max_len = 0;
  5. $len = 0;
  6. // echo strtr('hello world', ['he' => 'th', 'hello' => 'hi']);
  7. $pats = ['he', 'hello'];
  8. foreach($pats as $v){
  9. $len = strlen($v);
  10. if($len > $max_len) {
  11. $max_len = $len;
  12. }
  13. if($len < $min_len) {
  14. $min_len = $len;
  15. }
  16. $num_bitset[intdiv($len,8)] |= 1 << ($len%8);
  17. $bitset[intdiv(ord($v[0]),8)] |= 1 << (ord($v[0])%8);
  18. }
  19. // print_r(array_unique($num_bitset));
  20. // print_r(array_unique($bitset));
  21. // 例如我们匹配hello,首字符是h,长度5
  22. // 以下两行就是以上C语言的while循环里面两个if判断
  23. echo $bitset[intdiv(ord('h'),8)] & 1 << (ord('h')%8),PHP_EOL;
  24. 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. redis 清空缓存

    redis 清空缓存 Redis 命令: flushall --> 清空整个 Redis 服务器的数据(删除所有数据库的所有 key ) flushdb --> 清空当前数据库中的所有 k ...

  2. 开涛spring3(6.9) - 对JDBC的支持 之 7.1 概述

    7.1  概述 7.1.1  JDBC回顾 传统应用程序开发中,进行JDBC编程是相当痛苦的,如下所示: //cn.javass.spring.chapter7. TraditionalJdbcTes ...

  3. 未能加载文件或程序集Newtonsoft.Json, Version=4.5.0.0

    1.打开 程序管理器控制台  输入 PM> install-package newtonsoft.json 2.查看bin文件中是否有 newtonsoft.json.dll文件 3.在Web. ...

  4. ECMAScript 6.0 简介

    ECMAScript 6.0 在es6中有 许多语法.还有lambda的使用.以及 class 的使用 还有一些新的对象来解决一些事情 可以提高开发效率 但更重要的是 颠覆 javascript 在你 ...

  5. JS 使用 splice() 对数组去重

    一 问题 有如下 js 数组 connect_clients,需要在去掉所有元素中 user_id, goods_id 这两者的值都相同的元素. [ { id: 'eff040fb-92bc-4f24 ...

  6. 深入分析Java的String类的方法与特点

    字符串是任何编程语言都必须支持的变量类型,有些编程语言是直接提供了原生的变量类型,有些编程语言则使用语法特性以 SDK 的形式提供支持.在Java编程平台中,对字符串的支持使用了后者的形式,就是通过在 ...

  7. scrapy调试时出现 ImportError: No module named win32api

    windows下利用scrapy(python2.7)写爬虫,运行 scrapy crawl dmoz 命令时提示:exceptions.ImportError: No module named wi ...

  8. C#开发移动应用系列(2.使用WebView搭建WebApp应用)

    前言 上篇文章地址:C#开发移动应用系列(1.环境搭建) 嗯..一周了 本来打算2天一更的 - - ,结果 出差了..请各位原谅.. 今天我们来讲一下使用WebView搭建WebApp应用. 说明一下 ...

  9. php简单的文件操作

    (1)先要想好要操作哪个文件? (2)确定文件的路径? (3)要有什么文件管理功能? 一.先做一下简单的查看文件功能,文件中的文件和文件夹都显示,但是双击文件夹可以显示下一级子目录,双击"返 ...

  10. 【转载】图文详解 IntelliJ IDEA 15 创建普通 Java Web 项目

    第 1 部分:新建一个 Java Web Application 项目 File -> New -> Project-,请选择 Java EE 这个模块下的 Web Application ...