PHP源碼閱讀strtr

strtr

轉換字符串中特定的字符,可是這個函數使用的方式多種。php

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


時間複雜度
git

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

源碼

如下根據每種狀況逐一分析源碼。
第一種、第二種,也是最經常使用的,但第二種,只有’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字符串。app

第三種,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)++;
}

 

第四種,經過數組替換多個字符串,這種是各類狀況效率最差的post

// 先構造全部 被替換的字符串
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語言代碼spa

$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翻譯

原創文章,轉載請註明來源

相關文章
相關標籤/搜索