下面这个文本是经过 HTML 编码的文本, 表示的是一些汉字的 Unicode 编码。在 HTML 中使用 Unicode 编码可以用 &#
后跟 Unicode 码点的十进制值来表示特定字符。HTML 中的 &#
编码 (称为字符实体)
# 发邀请区版规
发邀区版规
UTF-16 与 UCS-2 的区别
UCS-2 和 UTF-16 都是 Unicode 的字符编码
Unicode 介绍
Unicode 通过明确的名称和称其为代码点的整数来识别字符。例如, ©
字符被命名为 “copyright sign”, 其码位为 U+00A9
(0xA9
, 十进制为 169
)
Unicode 代码空间分为 17 个平面 (plane), 每个平面有 2^16 (65,536) 个码位 (code point)。
第一个 Unicode 平面 (码位从 U+0000
至 U+FFFF
) 包含了最常用的字符, 该平面被称为基本多文种平面 (Basic Multilingual Plane), 缩写为 BMP。其他平面称为辅助平面 (Supplementary Planes)
只需要记住, 有 BMP 字符和非 BMP 字符, 后者也称为补充字符或星体字符。
UCS-2
UCS-2 (2-byte Universal Character Set) 是固定长度编码方式, UCS-2 仅简单的使用一个 16 位代码单元来表示码位, 也就是 0
到 0xFFFF
码位范围内 (即 BMP), 它和 UTF-16 基本一致
不能正确处理 emoji (4 字节字符), 因为它依赖于 UCS-2
编码, 这种编码方式不支持 3 字节及以上的字符
UTF-16
UTF-16 (16-bit Unicode Transformation Format) 是 UCS-2 的拓展, 它产生一个可变长度结果。它可以表示 BMP 以外的字符。UTF-16 使用一个或者两个 16 位的码元来表示码位, 这样就可以从 0
到 0x10FFFF
范围内的码位进行编码
简单的说, UTF-16 可看成是 UCS-2 的父集。在没有辅助平面字符 (surrogate code points) 前, UTF-16 与 UCS-2 所指的是同一的意思。 (严格的说这并不正确, 因为在 UTF-16 中从 U+D800
到 U+DFFF
的码位不对应于任何字符, 而在使用 UCS-2 的时代, U+D800
到 U+DFFF
内的值被占用) 但当引入辅助平面字符后, 就称为 UTF-16 了。
Unicode 编码函数
此函数将原始中文字符串 (支持指定编码) 转换为 Unicode 编码字符串, 格式为指定前缀和后缀 (默认为 &#
和 ;
), 可用于显示特殊字符或多语言场景下的 Unicode 表示
GBK 转 HTML 实体
假设所有字符都在基本多文种平面 (BMP) 内, 即每个字符正好占用两个字节, 而无需考虑代理对
使用 UCS-2BE
, 保证字符按大端序编码 (big-endian)。这将确保每个字符的高位字节在前、低位字节在后, 与 Unicode 码点顺序一致
/**
* $str 原始中文字符串
* $encoding 原始字符串的编码, 默认 GBK
* $prefix 编码后的前缀, 默认 "&#"
* $postfix 编码后的后缀, 默认 ";"
*/
function unicode_encode($str, $encoding = 'GBK', $prefix = '&#', $postfix = ';') {
$str = iconv($encoding, 'UCS-2BE', $str);
$arrstr = str_split($str, 2);
$unistr = '';
for ($i = 0, $len = count($arrstr); $i < $len; $i++) {
$dec = hexdec(bin2hex($arrstr[$i]));
$unistr .= $prefix . $dec . $postfix;
}
return $unistr;
}
这段代码会将字符串转换为 UCS-2BE
编码, 但 UCS-2BE
是一个 2 字节编码, 只适用于基本的多语言平面 (BMP) 字符。它 无法正确处理 3 字节或 4 字节的 UTF-8 字符, 如汉字 和 emoji (通常是 4 字节的 Unicode 字符)。因此, 任何 4 字节的字符 (比如 emoji) 在转换过程中会丢失或被错误编码。
UTF-8 转 HTML 实体
可以使用 mb_convert_encoding
将字符串转换为 HTML-ENTITIES 十进制编码
function unicode_encode($str, $encoding = 'UTF-8') {
// 使用 mb_convert_encoding 将 UTF-8 字符串转换为 HTML-ENTITIES
$str = mb_convert_encoding($str, 'HTML-ENTITIES', $encoding);
return $str;
}
直接处理 UTF-8 字符
当 mb_convert_encoding
函数不可用代替方案, 可能是因为 PHP 没有安装或启用 mbstring
扩展。你可以尝试另一种方法, 不依赖 mbstring
不使用 mb_convert_encoding
, 而是直接处理 UTF-8 编码的字符串, 将每个字符逐字节转换为十进制 Unicode 值
function unicode_encode($str, $encoding = 'UTF-8', $prefix = '&#', $postfix = ';') {
$str = iconv($encoding, 'UTF-8', $str);
$unistr = '';
// 遍历每个 UTF-8 字符
for ($i = 0, $len = strlen($str); $i < $len; $i++) {
$ord = ord($str[$i]);
// 根据 UTF-8 编码的首字节判断字符字节长度
if ($ord < 128) { // 1字节字符 (ASCII)
$unistr .= $prefix . $ord . $postfix;
} elseif ($ord < 224) { // 2字节字符
$code = (($ord & 0x1F) << 6) | (ord($str[++$i]) & 0x3F);
$unistr .= $prefix . $code . $postfix;
} elseif ($ord < 240) { // 3字节字符
$code = (($ord & 0x0F) << 12) | ((ord($str[++$i]) & 0x3F) << 6) | (ord($str[++$i]) & 0x3F);
$unistr .= $prefix . $code . $postfix;
} else { // 4字节字符 (不常见, 但包括一些 emoji 等)
$code = (($ord & 0x07) << 18) | ((ord($str[++$i]) & 0x3F) << 12) | ((ord($str[++$i]) & 0x3F) << 6) | (ord($str[++$i]) & 0x3F);
$unistr .= $prefix . $code . $postfix;
}
}
return $unistr;
}
这个代码能够逐字节地解析 UTF-8 编码字符, 并根据字符的字节长度 (1 到 4 字节) 来确定如何解析它们。对于 4 字节字符 (如 emoji), 它使用正确的位运算将其转换为相应的 Unicode 编码。因此, 它能够处理包含 emoji 的字符串, 并输出正确的 Unicode 编码。
emoji 字符通常是 UTF-8 编码的 4 字节字符, 代码能够识别并正确转换为 Unicode 编码。例如, 😍
(心形眼睛 emoji) 会被正确转换为 😍
Unicode 解码函数
HTML 实体 转 GBK
该函数将 Unicode 编码字符串 (带指定前后缀) 还原为指定编码的原始字符串。示例中的默认编码为 GBK
/**
* $str Unicode编码后的字符串
* $decoding 原始字符串的编码, 默认 GBK
* $prefix 编码字符串的前缀, 默认 "&#"
* $postfix 编码字符串的后缀, 默认 ";"
*/
function unicode_decode($unistr, $encoding = 'GBK', $prefix = '&#', $postfix = ';') {
$arruni = explode($prefix, $unistr);
$unistr = '';
for ($i = 1, $len = count($arruni); $i < $len; $i++) {
if (strlen($postfix) > 0) {
$arruni[$i] = substr($arruni[$i], 0, strlen($arruni[$i]) - strlen($postfix));
}
$temp = intval($arruni[$i]);
$unistr .= ($temp < 256) ? chr(0) . chr($temp) : chr($temp / 256) . chr($temp % 256);
}
return iconv('UCS-2BE', $encoding, $unistr);
}
HTML 实体 转 UTF-8
要将通过 unicode_encode
编码后的 Unicode 编码字符串解码回原始的字符 (包括 emoji), 可以通过解析这些 Unicode 编码并将它们转换回相应的字符
function unicode_decode($str, $encoding = 'UTF-8') {
$str = mb_convert_encoding($str, $encoding, 'HTML-ENTITIES');
return $str;
}
直接处理 UTF-8 字符
不依赖 mb_convert_encoding
function unicode_decode($str, $encoding = 'UTF-8', $prefix = '&#', $postfix = ';') {
$pattern = '/' . preg_quote($prefix) . '(\d+)' . preg_quote($postfix) . '/';
return preg_replace_callback($pattern, function ($matches) use ($encoding) {
$code = intval($matches[1]);
if ($code >= 0x10000) {
// 对于大于 0xFFFF 的 Unicode 编码 (4 字节字符, 如 emoji)
$code -= 0x10000;
$highSurrogate = 0xD800 | ($code >> 10);
$lowSurrogate = 0xDC00 | ($code & 0x3FF);
$packed = pack('v', $highSurrogate) . pack('v', $lowSurrogate);
return iconv('UTF-16LE', $encoding, $packed);
} else {
// 对于小于 0xFFFF 的 Unicode 字符
return iconv('UCS-4LE', $encoding, pack('V', $code));
}
}, $str);
}
其他实现
实现更简洁, 专门针对 &#...;
格式的 Unicode 实体解码, 编码固定为 UTF-8
function unicode_decode($unistr) {
$arr = preg_split("/(&#[0-9]*;)/", $unistr, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$restr = '';
foreach ($arr as $key => $value) {
if (strstr($value, '&#')) {
$unistr = '';
$arruni = explode('&#', $value);
$arruni = substr($arruni[1], 0, strlen($arruni[1]) - 1);
$temp = intval($arruni);
$unistr .= ($temp < 256) ? chr(0) . chr($temp) : chr($temp / 256) . chr($temp % 256);
$restr .= iconv('UCS-2BE', 'UTF-8', $unistr);
} else {
$restr .= $value;
}
}
return $restr;
}
测试
源代码: example.php
UTF-8 字符串测试
header('Content-Type: text/html; charset=UTF-8');
function pr($data) {
echo '<xmp>';
var_dump($data);
echo '</xmp>';
}
$str = '哈哈😍';
pr($str);
$unistr = unicode_encode($str);
pr($unistr);
$str2 = unicode_decode($unistr);
pr($str2);
输出结果
string(10) "哈哈😍"
string(25) "哈哈😍"
string(10) "哈哈😍"
GBK 字符串测试
注意: GBK 在 UTF-8 下显示的乱码!可以在 php 文件头加入 header()
GBK 编码不支持 emoji
header('Content-Type: text/html; charset=GBK');
$str = '哈哈';
pr($str);
$unistr = unicode_encode($str);
pr($unistr);
$str2 = unicode_decode($unistr);
pr($str2);
$gbk_str = iconv('UTF-8', 'GBK', $str);
pr($gbk_str);
$gbk_unistr = unicode_encode($gbk_str, 'GBK');
pr($gbk_unistr);
$gbk_str2 = unicode_decode($gbk_unistr, 'GBK');
pr($gbk_str2);
输出结果
string(6) "鍝堝搱"
string(16) "哈哈"
string(6) "鍝堝搱"
string(4) "哈哈"
string(16) "哈哈"
string(4) "哈哈"
其它后缀、前缀测试
$str = '哈哈😍';
pr($str);
$prefix_unistr = unicode_encode($str, 'UTF-8', "\\u", '');
pr($prefix_unistr);
$profix_unistr2 = unicode_decode($prefix_unistr, 'UTF-8', "\\u", '');
pr($profix_unistr2);
输出结果
string(10) "哈哈😍"
string(22) "\u21704\u21704\u128525"
string(10) "哈哈😍"
原文
php 中文unicode 互转
UTF-16与UCS-2的区别
JavaScript 的内部字符编码:UCS-2 还是 UTF-16?
在线字符统计:支持Unicode、URL、CSS实体、HTML实体、Base64 等编码/解码 - 工具 - CodePlayer
PHP 在线工具 | 菜鸟工具