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

PHP 純代碼實現 UTF-8 ↔ CP1251 (Windows-1251) 轉換,無需 iconv/mbstring 擴展

     編輯:PHP 相關知識

適用場景

共享主機/受限環境禁用 iconv、mbstring 擴展,需在純 PHP 中實現 **UTF-8 ⇄ Windows-1251 (CP1251)** 互轉(常見於俄語/西里爾字符處理)。

核心原理

  • CP1251 是單字節編碼(0x00-0xFF),西里爾字母集中在 0x80-0xFF。
  • UTF-8 多字節編碼,西里爾字母範圍 U+0400–U+04FF(2 字節序列:0xD0 0x90–0xBF, 0xD1 0x80–0xBF)。
  • 建立 **雙向映射表**(代碼點 ↔ 字節),逐字節/逐碼點轉換。

完整可用代碼(含查找表優化)

<?php
/**
 * UTF-8 → CP1251
 * @param string $utf8 合法 UTF-8 字符串
 * @return string CP1251 字節流
 */
function utf8_to_cp1251(string $utf8): string {
    static $map = null;
    if ($map === null) {
        // 建立 Unicode 碼點 → CP1251 字節 映射表(僅西里爾區塊 + ASCII)
        $map = [];
        // ASCII 0-127 直接通過
        for ($i = 0; $i < 128; $i++) $map[$i] = chr($i);
        // 西里爾大寫 U+0410–U+042F → 0xC0–0xDF
        for ($i = 0x410; $i <= 0x42F; $i++) $map[$i] = chr(0xC0 + ($i - 0x410));
        // 西里爾小寫 U+0430–U+044F → 0xE0–0xFF
        for ($i = 0x430; $i <= 0x44F; $i++) $map[$i] = chr(0xE0 + ($i - 0x430));
        // 特殊字符
        $map[0x401] = chr(0xA8); // Ё
        $map[0x451] = chr(0xB8); // ё
        $map[0x402] = chr(0xAF); // Ђ
        $map[0x452] = chr(0xBF); // ђ
        $map[0x403] = chr(0xAA); // Ѓ
        $map[0x453] = chr(0xBA); // ѓ
        $map[0x404] = chr(0xAC); // Є
        $map[0x454] = chr(0xBC); // є
        $map[0x405] = chr(0xAD); // Ѕ
        $map[0x455] = chr(0xBD); // ѕ
        $map[0x406] = chr(0xAE); // І
        $map[0x456] = chr(0xBE); // і
        $map[0x407] = chr(0xA2); // Ї
        $map[0x457] = chr(0xB2); // ї
        $map[0x408] = chr(0xA3); // Ј
        $map[0x458] = chr(0xB3); // ј
        $map[0x409] = chr(0xA4); // Љ
        $map[0x459] = chr(0xB4); // љ
        $map[0x40A] = chr(0xA5); // Њ
        $map[0x45A] = chr(0xB5); // њ
        $map[0x40B] = chr(0xA6); // Ћ
        $map[0x45B] = chr(0xB6); // ћ
        $map[0x40C] = chr(0xA7); // Ќ
        $map[0x45C] = chr(0xB7); // ќ
        $map[0x40E] = chr(0xA9); // Ў
        $map[0x45E] = chr(0xB9); // ў
        $map[0x40F] = chr(0xAB); // Џ
        $map[0x45F] = chr(0xBB); // џ
        // 其餘字符映射為 '?' (0x3F)
    }

    $len = strlen($utf8);
    $out = '';
    for ($i = 0; $i < $len; ) {
        $byte = ord($utf8[$i]);
        if ($byte < 128) { // ASCII
            $out .= chr($byte);
            $i++;
        } elseif (($byte & 0xE0) === 0xC0) { // 2 字節序列
            if ($i + 1 >= $len) { $out .= '?'; break; }
            $byte2 = ord($utf8[$i+1]);
            if (($byte2 & 0xC0) !== 0x80) { $out .= '?'; $i += 2; continue; }
            $codepoint = (($byte & 0x1F) << 6) | ($byte2 & 0x3F);
            $out .= $map[$codepoint] ?? '?';
            $i += 2;
        } elseif (($byte & 0xF0) === 0xE0) { // 3 字節(西里爾不在這裡,直接 ?)
            $i += 3; $out .= '?';
        } elseif (($byte & 0xF8) === 0xF0) { // 4 字節
            $i += 4; $out .= '?';
        } else {
            $i++; $out .= '?';
        }
    }
    return $out;
}

/**
 * CP1251 → UTF-8
 * @param string $cp1251 CP1251 字節流
 * @return string UTF-8 字符串
 */
function cp1251_to_utf8(string $cp1251): string {
    static $map = null;
    if ($map === null) {
        $map = [];
        // ASCII
        for ($i = 0; $i < 128; $i++) $map[chr($i)] = chr($i);
        // 0xC0-0xDF → U+0410-U+042F
        for ($i = 0xC0; $i <= 0xDF; $i++) $map[chr($i)] = "\xD0".chr(0x90 + ($i - 0xC0));
        // 0xE0-0xFF → U+0430-U+044F
        for ($i = 0xE0; $i <= 0xFF; $i++) $map[chr($i)] = "\xD1".chr(0x80 + ($i - 0xE0));
        // 特殊
        $map[chr(0xA8)] = "\xD0\x81"; // Ё
        $map[chr(0xB8)] = "\xD1\x91"; // ё
        $map[chr(0xAF)] = "\xD0\x82"; // Ђ
        $map[chr(0xBF)] = "\xD1\x92"; // ђ
        $map[chr(0xAA)] = "\xD0\x83"; // Ѓ
        $map[chr(0xBA)] = "\xD1\x93"; // ѓ
        $map[chr(0xAC)] = "\xD0\x84"; // Є
        $map[chr(0xBC)] = "\xD1\x94"; // є
        $map[chr(0xAD)] = "\xD0\x85"; // Ѕ
        $map[chr(0xBD)] = "\xD1\x95"; // ѕ
        $map[chr(0xAE)] = "\xD0\x86"; // І
        $map[chr(0xBE)] = "\xD1\x96"; // і
        $map[chr(0xA2)] = "\xD0\x87"; // Ї
        $map[chr(0xB2)] = "\xD1\x97"; // ї
        $map[chr(0xA3)] = "\xD0\x88"; // Ј
        $map[chr(0xB3)] = "\xD1\x98"; // ј
        $map[chr(0xA4)] = "\xD0\x89"; // Љ
        $map[chr(0xB4)] = "\xD1\x99"; // љ
        $map[chr(0xA5)] = "\xD0\x8A"; // Њ
        $map[chr(0xB5)] = "\xD1\x9A"; // њ
        $map[chr(0xA6)] = "\xD0\x8B"; // Ћ
        $map[chr(0xB6)] = "\xD1\x9B"; // ћ
        $map[chr(0xA7)] = "\xD0\x8C"; // Ќ
        $map[chr(0xB7)] = "\xD1\x9C"; // ќ
        $map[chr(0xA9)] = "\xD0\x8E"; // Ў
        $map[chr(0xB9)] = "\xD1\x9E"; // ў
        $map[chr(0xAB)] = "\xD0\x8F"; // Џ
        $map[chr(0xBB)] = "\xD1\x9F"; // џ
    }

    $len = strlen($cp1251);
    $out = '';
    for ($i = 0; $i < $len; $i++) {
        $ch = $cp1251[$i];
        $out .= $map[$ch] ?? '?';
    }
    return $out;
}

// 測試
$utf8 = "Привет, мир! Hello";
$cp1251 = utf8_to_cp1251($utf8);
$back = cp1251_to_utf8($cp1251);
echo "原始: $utf8\n";
echo "CP1251(hex): " . bin2hex($cp1251) . "\n";
echo "回轉: $back\n";
echo "一致: " . ($utf8 === $back ? '是' : '否') . "\n";

性能與優化建議

  • 靜態查找表 $map 僅首次調用構建,後續直接複用,O(1) 查找。
  • 大字符串可分塊處理,避免單次分配過大內存。
  • 若環境允許,**永遠優先用 iconv 或 mb_convert_encoding**(C 級擴展,速度快 10-50 倍、支持完整 Unicode)。

擴展至其他單字節編碼

同理可構建 UTF-8 ⇄ CP1252 (Latin1)、UTF-8 ⇄ GBK(雙字節,需雙字節查找表)等純 PHP 轉換器。但 **GBK/Big5 等雙字節編碼映射表極大(萬級條目),純 PHP 實現極慢,強烈建議啟用 mbstring**。

總結

在無 iconv/mbstring 環境下,通過 **Unicode 碼點 ↔ 目標編碼字節** 的靜態映射表,可高效實現 **UTF-8 ⇄ CP1251** 互轉。代碼已封裝為無依賴函數,可直接集成至項目。生產環境若可能,**務必啟用 mbstring 擴展**。

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