在 Unicode 普及前,中文系統廣泛使用 GB2312/GBK 編碼。遷移至 UTF-8 時,常需處理歷史數據編碼轉換。本文解析一個基於代碼表映射的 GB2UTF8 類實現原理,並對比現代 PHP 內建轉換函數的優勢。
<?php
class GB2UTF8 {
public $gb = ''; // 輸入 GB2312 字符串
public $utf8 = ''; // 輸出 UTF-8 字符串
private $codeTable = []; // GB2312 → Unicode 映射表
private $errorMsg = '';
public function __construct($inStr = '') {
$this->gb = $inStr;
$this->loadCodeTable();
if ($this->gb !== '') $this->convert();
}
private function loadCodeTable($file = 'gb2312.txt') {
$lines = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!$lines) {
$this->errorMsg = "Code table file not found: $file";
return false;
}
foreach ($lines as $line) {
// 格式: GB碼(4位十六進制) Unicode碼(4位十六進制)
// 例: A1A1 4E2D (「中」字)
$gbCode = hexdec(substr($line, 0, 4));
$uniCode = hexdec(substr($line, 5, 4));
$this->codeTable[$gbCode] = $uniCode;
}
return true;
}
private function convert() {
$this->utf8 = '';
$str = $this->gb;
while (strlen($str) > 0) {
$byte = ord($str[0]);
if ($byte > 127) { // 多位元組字符 (GBK 雙字節)
if (strlen($str) < 2) break;
$charBytes = substr($str, 0, 2);
$str = substr($str, 2);
$gbCode = hexdec(bin2hex($charBytes)) - 0x8080; // 還原區位碼
if (isset($this->codeTable[$gbCode])) {
$uniCode = $this->codeTable[$gbCode];
$this->utf8 .= $this->unicodeToUtf8($uniCode);
} else {
$this->utf8 .= '?'; // 無映射替換
}
} else { // ASCII 單字節
$this->utf8 .= $str[0];
$str = substr($str, 1);
}
}
return $this->utf8;
}
private function unicodeToUtf8($codepoint) {
// Unicode 碼點 → UTF-8 字節序列
if ($codepoint < 0x80) {
return chr($codepoint);
} elseif ($codepoint < 0x800) {
return chr(0xC0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3F));
} elseif ($codepoint < 0x10000) {
return chr(0xE0 | ($codepoint >> 12)) .
chr(0x80 | (($codepoint >> 6) & 0x3F)) .
chr(0x80 | ($codepoint & 0x3F));
} elseif ($codepoint < 0x200000) {
return chr(0xF0 | ($codepoint >> 18)) .
chr(0x80 | (($codepoint >> 12) & 0x3F)) .
chr(0x80 | (($codepoint >> 6) & 0x3F)) .
chr(0x80 | ($codepoint & 0x3F));
}
return '?';
}
}
A1A1 4E2D # 中
A1A2 56FD # 國
A1A3 4EBA # 人
A1A4 6C11 # 民
...
# 共 6763 個漢字 + 682 個符號
<?php
require_once 'GB2UTF8.class.php';
// 方式 1: 構造函數自動轉換
$converter = new GB2UTF8("中文測試");
echo $converter->utf8; // UTF-8 輸出
// 方式 2: 手動調用
$converter = new GB2UTF8();
$converter->gb = file_get_contents('legacy_gbk.txt');
$converter->convert();
file_put_contents('converted_utf8.txt', $converter->utf8);
// GD 圖片繪製中文
header('Content-Type: image/png');
$im = imagecreatetruecolor(400, 100);
$black = imagecolorallocate($im, 0, 0, 0);
$red = imagecolorallocate($im, 255, 0, 0);
$converter = new GB2UTF8("你好世界");
imagettftext($im, 24, 0, 10, 40, $red, 'SimHei.ttf', $converter->utf8);
imagepng($im);
imagedestroy($im);
| 函數/擴展 | 支援編碼 | 性能 | 依賴 | 推薦度 |
|---|---|---|---|---|
mb_convert_encoding() | 100+ (含 GBK, GB2312, UTF-8, Big5...) | 高 (C 實現) | mbstring | ⭐⭐⭐⭐⭐ |
iconv() | GNU libiconv 支援的所有編碼 | 高 | iconv | ⭐⭐⭐⭐ |
UConverter (intl) | ICU 全集 | 極高 | intl (PHP 5.5+) | ⭐⭐⭐⭐⭐ |
| 手工映射類 | 僅代碼表覆蓋範圍 | 低 (PHP 循環) | 無 | ⭐ (教學/特殊環境) |
<?php
// mbstring (最常用)
$utf8 = mb_convert_encoding($gbkStr, 'UTF-8', 'GBK');
// 或自動檢測
$utf8 = mb_convert_encoding($str, 'UTF-8', 'auto');
// iconv (需處理 //IGNORE //TRANSLIT)
$utf8 = iconv('GBK', 'UTF-8//IGNORE', $gbkStr);
// intl UConverter (最高性能、最準確)
$conv = new UConverter('UTF-8', 'GB18030'); // GB18030 兼容 GBK
$utf8 = $conv->transcode($gbkStr);
// 批量轉換文件
$files = glob('legacy/*.txt');
foreach ($files as $f) {
$content = file_get_contents($f);
$utf8 = mb_convert_encoding($content, 'UTF-8', 'GBK');
file_put_contents('utf8/' . basename($f), $utf8);
}
<?php
// 檢測編碼 (mbstring)
$encoding = mb_detect_encoding($str, ['UTF-8', 'GBK', 'GB2312', 'BIG5'], true);
if ($encoding !== 'UTF-8') {
$str = mb_convert_encoding($str, 'UTF-8', $encoding);
}
// 常見坑
// 1. GB2312 ≠ GBK:GBK 是 GB2312 超集,含更多字符。轉碼用 'GBK' 更安全
// 2. 瀏覽器編碼聲明必須一致:Header + Meta 双重保險
header('Content-Type: text/html; charset=UTF-8');
//
// 3. 資料庫連線編碼
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=gbk', 'user', 'pass');
// 或查詢前執行
$pdo->exec('SET NAMES gbk');
手工映射類適合理解編碼轉換原理、極簡環境無擴展支援場景。生產環境請直接使用 mb_convert_encoding() 或 UConverter,享受 C 級性能、完整字符集覆蓋、標準化錯誤處理。遷移項目建議:統一內部編碼為 UTF-8,邊界處(輸入/輸出/資料庫/外部 API)做轉碼適配。