strtr 是 PHP 內建字串轉換函數,支援「單字元對應」與「陣列字典替換」兩種模式。本文從 PHP 7/8 核心原始碼(C 語言)出發,詳細解析 php_strtr 與 php_strtr_array 實作邏輯,揭示其效能特性與適用場景。
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)));
}
}
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;
}
strtr('abcdaaabcd', 'ab', 'ef') → efcdeeeefcd(a→e, b→f)。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);
}
minlen、maxlen。maxlen 長度的子串,遞減至 minlen。確保「最長鍵優先」,避免短鍵搶先匹配。(maxlen-minlen+1) 次。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 陣列模式(多位元組安全)。strtr 最長匹配語義正確且效能佳。php-aho-corasick 擴充)或資料庫全文搜尋。strtr 陣列模式以「最長匹配優先」語義見長,核心演算法為「預處理統計長度 → HashTable 索引 → 貪婪掃描」。其效能對 鍵長差異極度敏感,固定長度字典可達百萬級字串毫秒級處理;混合長度時需權衡語義正確性與速度,必要時改用分層替換或專用演算法。