首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

深入 PHP 核心:strtr 函數原始碼分析與效能特性解析

     編輯:PHP 相關知識

strtr 是 PHP 內建字串轉換函數,支援「單字元對應」與「陣列字典替換」兩種模式。本文從 PHP 7/8 核心原始碼(C 語言)出發,詳細解析 php_strtr 與 php_strtr_array 實作邏輯,揭示其效能特性與適用場景。

一、函數進入點:PHP_FUNCTION(strtr)

PHP_FUNCTION(strtr) {
    zval **str, **from, **to;
    int ac = ZEND_NUM_ARGS();

    // 參數數量檢查:2 或 3 個
    if (ac < 2 || ac > 3 || zend_get_parameters_ex(ac, &str, &from, &to) == FAILURE) {
        WRONG_PARAM_COUNT;
    }

    // 兩參數模式:第二參數必須是陣列
    if (ac == 2 && Z_TYPE_PP(from) != IS_ARRAY) {
        php_error_docref(NULL, E_WARNING, "The second argument is not an array.");
        RETURN_FALSE;
    }

    convert_to_string_ex(str);

    // 空字串快速返回
    if (Z_STRLEN_PP(str) == 0) {
        RETURN_EMPTY_STRING();
    }

    if (ac == 2) {
        // 陣列模式:呼叫 php_strtr_array
        php_strtr_array(return_value, Z_STRVAL_PP(str), Z_STRLEN_PP(str), HASH_OF(*from));
    } else {
        // 三參數模式:單字元對應,呼叫 php_strtr
        convert_to_string_ex(from);
        convert_to_string_ex(to);
        ZVAL_STRINGL(return_value, Z_STRVAL_PP(str), Z_STRLEN_PP(str), 1);
        php_strtr(Z_STRVAL_P(return_value),
                  Z_STRLEN_P(return_value),
                  Z_STRVAL_PP(from),
                  Z_STRVAL_PP(to),
                  MIN(Z_STRLEN_PP(from), Z_STRLEN_PP(to)));
    }
}

二、單字元對應模式:php_strtr

PHPAPI char *php_strtr(char *str, int len, char *str_from, char *str_to, int trlen) {
    int i;
    unsigned char xlat[256];  // 256 位元組查找表

    if ((trlen < 1) || (len < 1)) return str;

    // 初始化:xlat[i] = i(身分映射)
    for (i = 0; i < 256; xlat[i] = i, i++);

    // 建立 from -> to 映射
    for (i = 0; i < trlen; i++) {
        xlat[(unsigned char)str_from[i]] = str_to[i];
    }

    // 逐位元組替換
    for (i = 0; i < len; i++) {
        str[i] = xlat[(unsigned char)str[i]];
    }
    return str;
}

特性分析

  • O(1) 查找:256 位元組查找表,單趟掃描完成替換。
  • 極快:適合單位元組編碼(ASCII、ISO-8859-1)的字元級轉換,如 ROT13、大小寫轉換、全半形轉換。
  • UTF-8 禁用:中文等多位元組字元會被拆成多個位元組單獨替換,導致亂碼。
  • 範例:strtr('abcdaaabcd', 'ab', 'ef') → efcdeeeefcd(a→e, b→f)。

三、陣列字典模式:php_strtr_array

static void php_strtr_array(zval *return_value, char *str, int slen, HashTable *hash) {
    // ... 變數宣告 ...
    smart_str result = {0};
    HashTable tmp_hash;

    // 1. 複製 hash 到 tmp_hash,並記錄鍵的最小/最大長度
    zend_hash_init(&tmp_hash, 0, NULL, NULL, 0);
    zend_hash_internal_pointer_reset_ex(hash, &hpos);
    while (zend_hash_get_current_data_ex(hash, (void**)&entry, &hpos) == SUCCESS) {
        switch (zend_hash_get_current_key_ex(hash, &string_key, &string_key_len, &num_key, 0, &hpos)) {
            case HASH_KEY_IS_STRING:
                len = string_key_len - 1; // 去除結束符
                if (len < 1) { zend_hash_destroy(&tmp_hash); RETURN_FALSE; }
                zend_hash_add(&tmp_hash, string_key, string_key_len, entry, sizeof(zval*), NULL);
                if (len > maxlen) maxlen = len;
                if (len < minlen) minlen = len;
                break;
            case HASH_KEY_IS_LONG:
                // 整數鍵轉字串
                Z_TYPE(ctmp) = IS_LONG; Z_LVAL(ctmp) = num_key;
                convert_to_string(&ctmp);
                len = Z_STRLEN(ctmp);
                zend_hash_add(&tmp_hash, Z_STRVAL(ctmp), len+1, entry, sizeof(zval*), NULL);
                zval_dtor(&ctmp);
                if (len > maxlen) maxlen = len;
                if (len < minlen) minlen = len;
                break;
        }
        zend_hash_move_forward_ex(hash, &hpos);
    }

    key = emalloc(maxlen + 1);
    pos = 0;

    // 2. 主掃描迴圈:從 pos 開始,嘗試匹配最長鍵
    while (pos < slen) {
        if ((pos + maxlen) > slen) maxlen = slen - pos;
        found = 0;
        memcpy(key, str + pos, maxlen);

        // 從 maxlen 遞減到 minlen
        for (len = maxlen; len >= minlen; len--) {
            key[len] = '\0';
            if (zend_hash_find(&tmp_hash, key, len+1, (void**)&trans) == SUCCESS) {
                // 找到匹配,取得替換值
                char *tval; int tlen;
                zval tmp;
                if (Z_TYPE_PP(trans) != IS_STRING) {
                    tmp = **trans; zval_copy_ctor(&tmp); convert_to_string(&tmp);
                    tval = Z_STRVAL(tmp); tlen = Z_STRLEN(tmp);
                } else {
                    tval = Z_STRVAL_PP(trans); tlen = Z_STRLEN_PP(trans);
                }
                smart_str_appendl(&result, tval, tlen);
                pos += len;      // 跳過已匹配長度
                found = 1;
                if (Z_TYPE_PP(trans) != IS_STRING) zval_dtor(&tmp);
                break;
            }
        }
        if (!found) {
            smart_str_appendc(&result, str[pos++]); // 無匹配,複製原字元
        }
    }

    efree(key);
    zend_hash_destroy(&tmp_hash);
    smart_str_0(&result);
    RETVAL_STRINGL(result.c, result.len, 0);
}

核心邏輯解析

  1. 預處理:將使用者陣列複製到內部 HashTable,同時統計 minlen、maxlen。
  2. 貪婪最長匹配:掃描 subject 時,優先嘗試 maxlen 長度的子串,遞減至 minlen。確保「最長鍵優先」,避免短鍵搶先匹配。
  3. HashTable O(1) 查找:每個長度嘗試一次 Hash 查找,最壞情況每位元組嘗試 (maxlen-minlen+1) 次。
  4. 智慧字串構建:使用 smart_str 動態擴充結果緩衝區,避免重複分配。

四、效能影響因子

因子影響說明
鍵長差異 (maxlen - minlen)🔴 高差異大 → 內層迴圈次數多 → CPU 增加
鍵的數量🟡 中HashTable 查找 O(1),但記憶體佔用增大
Subject 長度🟢 線性單趟掃描,O(n)
匹配命中率🟡 中命中即跳過多位元組,未命中逐字元複製

五、實測驗證:鍵長差異效應

<?php
$str = str_repeat('abcdefghijklmnopqrstuvwxyz', 1000); // ~26KB

// 情況 A:鍵長固定 2
$mapA = array_combine(range('aa','zz'), range('AA','ZZ')); // 676 個 2 字元鍵

// 情況 B:鍵長 1~10 混合
$mapB = $mapA;
$mapB['a'] = 'A';
$mapB['abcdefghij'] = 'LONG';

$t = microtime(true); strtr($str, $mapA); echo "固定長度: " . (microtime(true)-$t) . "\n";
$t = microtime(true); strtr($str, $mapB); echo "混合長度: " . (microtime(true)-$t) . "\n";
// 典型結果:混合長度比固定長度慢 2~3 倍

六、最佳實踐建議

  • 字元級轉換:優先用 strtr($str, $from, $to) 三參數模式(單位元組),或 str_replace 陣列模式(多位元組安全)。
  • 字典替換:若鍵長相近(如敏感詞庫多為 2~4 字),strtr 最長匹配語義正確且效能佳。
  • 避免極端鍵長差異:若有 1 字元與 50 字元鍵共存,考慮拆分為兩次替換:先處理長鍵,再處理短鍵。
  • UTF-8 環境:嚴禁三參數模式;陣列模式可用,但鍵值必須為合法 UTF-8 字串。
  • 超大字典(>1萬鍵):考慮 Aho-Corasick 自動機(如 php-aho-corasick 擴充)或資料庫全文搜尋。

七、總結

strtr 陣列模式以「最長匹配優先」語義見長,核心演算法為「預處理統計長度 → HashTable 索引 → 貪婪掃描」。其效能對 鍵長差異極度敏感,固定長度字典可達百萬級字串毫秒級處理;混合長度時需權衡語義正確性與速度,必要時改用分層替換或專用演算法。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved