PHP 內建 iconv/mb_convert_encoding 可處理編碼轉換,但理解 UTF-8 ↔ Unicode(UCS-2/UTF-16)底層邏輯,有助於處理特殊需求(如自定 BOM、端序、JavaScript escape() 相容)。本文提供完整純 PHP 實作函數庫。
| Unicode 範圍 | UTF-8 位元組數 | 位元模板 |
|---|---|---|
| U+0000 ~ U+007F | 1 | 0xxxxxxx |
| U+0080 ~ U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800 ~ U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 ~ U+10FFFF | 4 | 11110xxx 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'); // 測試
FF FE,Notepad/UE/大多數編輯器支援佳。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 等的完美互通。