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

PHP UTF-8 與 Unicode 互轉完整實作:支援 Big/Little Endian、BOM、escape/unescape

     編輯:PHP 綜合知識

前言

PHP 內建 iconv/mb_convert_encoding 可處理編碼轉換,但理解 UTF-8 ↔ Unicode(UCS-2/UTF-16)底層邏輯,有助於處理特殊需求(如自定 BOM、端序、JavaScript escape() 相容)。本文提供完整純 PHP 實作函數庫。

編碼原理速查

Unicode 範圍UTF-8 位元組數位元模板
U+0000 ~ U+007F10xxxxxxx
U+0080 ~ U+07FF2110xxxxx 10xxxxxx
U+0800 ~ U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

例:「漢」 U+6C49 → 二進位 0110 110001 001001 → 填入 3 字節模板 → E6 B1 89。

核心函數庫

<?php
/**
 * UTF-8 字串 → UCS-2(Unicode)字串
 * @param string $str UTF-8 編碼字串
 * @param string $order 'little'|'big' 位元組序,預設 little
 * @return string UCS-2 原始位元組字串(不含 BOM)
 */
function utf8ToUnicode($str, $order = "little") {
    $ucs2 = "";
    $len = strlen($str);
    for ($i = 0; $i < $len; $i++) {
        $c = ord($str[$i]);
        if ($c < 0x80) { // 1 byte
            $ucs2 .= ($order === 'little') ? chr($c).chr(0) : chr(0).chr($c);
        } elseif ($c < 0xE0) { // 2 bytes
            $c2 = ord($str[++$i]);
            $val = (($c & 0x1F) << 6) | ($c2 & 0x3F);
            $ucs2 .= ($order === 'little') ? chr($val & 0xFF).chr($val >> 8) : chr($val >> 8).chr($val & 0xFF);
        } elseif ($c < 0xF0) { // 3 bytes
            $c2 = ord($str[++$i]);
            $c3 = ord($str[++$i]);
            $val = (($c & 0x0F) << 12) | (($c2 & 0x3F) << 6) | ($c3 & 0x3F);
            $ucs2 .= ($order === 'little') ? chr($val & 0xFF).chr($val >> 8) : chr($val >> 8).chr($val & 0xFF);
        } else { // 4 bytes (超出 UCS-2 範圍,此處略過或轉換為代理對)
            $i += 3;
        }
    }
    return $ucs2;
}

/**
 * UCS-2 → UTF-8
 */
function unicodeToUtf8($str, $order = "little") {
    $utf8 = "";
    $len = strlen($str);
    for ($i = 0; $i < $len; $i += 2) {
        $val = ($order === 'little')
            ? (ord($str[$i]) | (ord($str[$i+1]) << 8))
            : ((ord($str[$i]) << 8) | ord($str[$i+1]));
        if ($val < 0x80) {
            $utf8 .= chr($val);
        } elseif ($val < 0x800) {
            $utf8 .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F));
        } else {
            $utf8 .= chr(0xE0 | ($val >> 12))
                   . chr(0x80 | (($val >> 6) & 0x3F))
                   . chr(0x80 | ($val & 0x3F));
        }
    }
    return $utf8;
}

/**
 * 單一 UTF-8 字元 → Unicode 碼值(十六進位字串)
 */
function utf2ucs($chr) {
    $n = strlen($chr);
    if ($n === 3) {
        $a = ord($chr[0]) & 0x1F;
        $b = ord($chr[1]) & 0x3F;
        $c = ord($chr[2]) & 0x3F;
        return dechex(($a << 12) | ($b << 6) | $c);
    } elseif ($n === 2) {
        $a = ord($chr[0]) & 0x1F;
        $b = ord($chr[1]) & 0x3F;
        return dechex(($a << 6) | $b);
    }
    return dechex(ord($chr));
}

/**
 * UTF-8 字串 → JavaScript escape() 格式(%uXXXX)
 */
function utf8Escape($str) {
    preg_match_all("/[\xC0-\xDF].|[\xE0-\xEF]..|[\x01-\x7F]+/", $str, $m);
    $out = "";
    foreach ($m[0] as $v) {
        $ord = ord($v[0]);
        if ($ord >= 0x80) {
            $out .= "%u" . str_pad(utf2ucs($v), 4, "0", STR_PAD_LEFT);
        } else {
            $out .= $v;
        }
    }
    return $out;
}

/**
 * JavaScript unescape() 相容解碼 → UTF-8
 */
function phpUnescape($esc) {
    preg_match_all("/%u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|[^%]+/", $esc, $m);
    $out = "";
    foreach ($m[0] as $v) {
        if ($v[0] !== '%') {
            $out .= $v;
        } elseif ($v[1] === 'u') {
            $code = hexdec(substr($v, 2));
            if ($code < 0x80) $out .= chr($code);
            elseif ($code < 0x800) $out .= chr(0xC0 | ($code >> 6)) . chr(0x80 | ($code & 0x3F));
            else $out .= chr(0xE0 | ($code >> 12)) . chr(0x80 | (($code >> 6) & 0x3F)) . chr(0x80 | ($code & 0x3F));
        } else {
            $out .= chr(hexdec(substr($v, 1, 2)));
        }
    }
    return $out;
}

/**
 * PHP 風格 unicode_escape(\uXXXX)編碼/解碼
 */
function phpEscape($str) {
    return preg_replace_callback('/[\x80-\xFF]/', function($m) {
        return '\u' . str_pad(dechex(ord($m[0])), 4, '0', STR_PAD_LEFT);
    }, $str);
}
function phpUnescape2($str) {
    return preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/', function($m) {
        return phpUnescape('%u' . $m[1]);
    }, $str);
}
?>

使用範例

$utf8 = "測試中文";
$ucs2le = utf8ToUnicode($utf8, 'little');
$ucs2be = utf8ToUnicode($utf8, 'big');

// 寫入檔案(自行加 BOM)
file_put_contents('test_le.txt', "\xFF\xFE" . $ucs2le); // Little Endian BOM
file_put_contents('test_be.txt', "\xFE\xFF" . $ucs2be); // Big Endian BOM

// 迴圈驗證
echo utf8Escape($utf8);        // %u6E2C%u8A66%u4E2D%u6587
echo phpUnescape("%u6E2C%u8A66"); // 測試

// PHP 風格
echo phpEscape($utf8);         // \u6e2c\u8a66\u4e2d\u6587
echo phpUnescape2('\u6e2c\u8a66'); // 測試

端序與 BOM 注意事項

  • Little Endian:低位在前(Intel x86 預設),BOM FF FE,Notepad/UE/大多數編輯器支援佳。
  • Big Endian:高位在前(網路序),BOM FE FF,僅 Notepad 原生完美識別,UE 需手動指定。
  • iconv("UTF-8", "UCS-2BE", $str) 輸出為 Big Endian,若需 Little Endian 請用 UCS-2LE 或自行位元組交換。

結語

掌握 UTF-8/Unicode 底層轉換,能在無 mbstring/iconv 環境下處理編碼問題,也能精確控制 BOM、端序,實現與 JavaScript escape/unescape、PHP json_encode 等的完美互通。

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