Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode
是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制
<https://baike.baidu.com/item/%E4%BA%8C%E8%BF%9B%E5%88%B6>
编码,以满足跨语言、跨平台进行文本转换、处理的要求。

 

  Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。目前的Unicode字符分为 17 组编排,0x0000 至
0x10FFFF,每组称为平面(Plane),而每平面拥有65536个码位,共1114112个。然而目前只用了少数平面。UTF-8、UTF-16、UTF-32
都是将数字转换到程序数据的编码方案。

 

  UTF-16 是 Unicode字符编码五层次模型的第三层:字符编码表的一种实现方式。即把 Unicode 字符集的抽象码位映射为 16
位长的整数的序列 (即码元),用于数据存储或传递。Unicode字符的码位,需要1个或者 2 个 16 位长的码元来表示,因此这是一个变长编码。

 

  上为 UTF-16
编码的介绍,红字部分强调它是一个变长编码;但实际上很多对编码有理解的人也都以为它是固定长度编码。这个错误的认知在日常编程中似乎不会有什么问题,因为常用中文字符都可以用
1 个 16 位长的码元表示。但是,中文有近 8 万个字符,而 1 个 16 位长的码元最大值仅是
65535(0xffff),所以超过一半的不常用中文字符被定义为扩展字符,这些字符需要用 2 个 16 位长的码元表示。

 

  UTF-16 编码以16位无符号整数为单位。我们把 Unicode 编码记作 U。编码规则如下:

 

  如果 U < 0x10000,U 的 UTF-16 编码就是 U 对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。

 

  如果 U >= 0x10000,我们先计算 U' = U - 0x10000,然后将 U' 写成二进制形式:yyyy yyyy yyxx xxxx
xxxx,U 的 UTF-16 编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

 

  为什么 U' 可以被写成 20 个二进制位?Unicode 的最大码位是 0x10FFFF,减去 0x10000 后,U'
的最大值是0xFFFFF,所以肯定可以用 20 个二进制
<https://baike.baidu.com/item/%E4%BA%8C%E8%BF%9B%E5%88%B6>位表示。例如:Unicode 编码
0x20C30,减去 0x10000 后,得到 0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的
y,用后 10 位依次替代模板中的 x,就得到:1101100001000011 1101110000110000,即 0xD843 0xDC30。

 

  按照上述规则,Unicode 编码 0x10000-0x10FFFF 的 UTF-16 编码有两个 WORD,第一个 WORD 的高 6 位是
110110,第二个 WORD 的高 6 位是 110111。可见,第一个 WORD 的取值范围(二进制)是 11011000 00000000到
11011011 11111111,即 0xD800-0xDBFF。第二个 WORD 的取值范围(二进制)是 11011100 00000000到
11011111 11111111,即 0xDC00-0xDFFF。

 

  为了将一个 WORD 的 UTF-16 编码与两 个WORD 的 UTF-16 编码区分开来,Unicode 编码的设计者将0xD800-0xDFFF
保留下来,并称为代理区(Surrogate):

D800-DBFF High Surrogates 高位替代
DC00-DFFF Low Surrogates 低位替代  
 

 

 


  高位替代就是指这个范围的码位是两个WORD的UTF-16编码的第一个WORD。低位替代就是指这个范围的码位是两个WORD的UTF-16编码的第二个WORD。

 

  上述是对 UTF-16 编码规则的说明,那如何实现它呢?下面使用 C# 代码演示如何 UTF-16 与 UTF-32 间互转:
public class Demo { internal const char HIGH_SURROGATE_START = '\ud800';
internal const char HIGH_SURROGATE_END = '\udbff'; internal const char
LOW_SURROGATE_START ='\udc00'; internal const char LOW_SURROGATE_END = '\udfff';
internal const int UNICODE_PLANE00_END = 0x00ffff; internal const int
UNICODE_PLANE01_START =0x10000; internal const int UNICODE_PLANE16_END =
0x10ffff; public static bool IsHighSurrogate(char c) { return ((c >=
HIGH_SURROGATE_START) && (c <= HIGH_SURROGATE_END)); } public static bool
IsLowSurrogate(char c) { return ((c >= LOW_SURROGATE_START) && (c <=
LOW_SURROGATE_END)); }public static char[] ConvertFromUtf32(int utf32) { //
U+00D800 ~ U+00DFFF 这个范围被 Unicode 定义为专用代理区,它们不能作为 Unicode 编码值。 if ((utf32 < 0
|| utf32 > UNICODE_PLANE16_END) || (utf32 >= HIGH_SURROGATE_START && utf32 <=
LOW_SURROGATE_END)) {throw new ArgumentOutOfRangeException("utf32"); } if
(utf32 < UNICODE_PLANE01_START) { // 这是一个基本字符。 return new char[] { (char)utf32
}; }// 这是一个扩展字符,需要将其转换为 UTF-16 中的代理项对。 utf32 -= UNICODE_PLANE01_START; return
new char[] { (char)((utf32 / 0x400) + HIGH_SURROGATE_START), (char)((utf32 %
0x400) + LOW_SURROGATE_START) }; } public static int ConvertToUtf32(char
highSurrogate,char lowSurrogate) { if (!IsHighSurrogate(highSurrogate)) { throw
new ArgumentOutOfRangeException("highSurrogate"); } if (!
IsLowSurrogate(lowSurrogate)) {throw new ArgumentOutOfRangeException("
lowSurrogate"); } return (((highSurrogate - HIGH_SURROGATE_START) * 0x400) +
(lowSurrogate - LOW_SURROGATE_START) + UNICODE_PLANE01_START); } }
  为何说 JDK 在这一方面错了十年呢?因为在 Java 7 时期,因为字符串架构设计不合理,误 utf-16 将以为是固定长度编码,而实际 utf-16
是可变长度编码,因为 char(word) 的最大值是 0xffff,而 Unicode 规范最大值是 0x10ffff,小概率出现的字符需要两个 char
才能表示。Java 后来意识到这个错误,并 Java 接下来的几个版本里,匆匆将字符串编码改为 utf8(实际是,判断如果有字符超出 0xffff,则使用
utf-8,否则还是继续 不正常的 utf-16 算法)。再后来 Java 才使用上了正常的 utf-16 编码。

 

 
  前两年有个段子,说只有 2000 元以上的手机才能在输入法打出某个汉字。原因正是这个。       这里附上由我的开源项目:.Net 平台的高性能
Json 解析库:Swifter.Json:https://github.com/Dogwei/Swifter.Json
<https://github.com/Dogwei/Swifter.Json>。希望大家支持一下。     最后附上 Swifter.Json 内部使用的
Utf16 与 Utf8 互转的源代码: public static unsafe class EncodingHelper { public const
char ASCIIMaxChar = (char)0x7f; public const int Utf8MaxBytesCount = 4; public
static int GetUtf8Bytes(char* chars, int length, byte* bytes) { var destination
= bytes; for (int i = 0; i < length; i++) { int c = chars[i]; if (c <= 0x7f) {
*destination = (byte)c; ++destination; } else if (c <= 0x7ff) { *destination = (
byte)(0xc0 | (c >> 6)); ++destination; *destination = (byte)(0x80 | (c & 0x3f
)); ++destination; } else if (c >= 0xd800 && c <= 0xdbff) { c = ((c & 0x3ff) <<
10) + 0x10000; ++i; if (i < length) { c |= chars[i] & 0x3ff; } *destination = (
byte)(0xf0 | (c >> 18)); ++destination; *destination = (byte)(0x80 | ((c >> 12)
&0x3f)); ++destination; *destination = (byte)(0x80 | ((c >> 6) & 0x3f)); ++
destination;*destination = (byte)(0x80 | (c & 0x3f)); ++destination; } else {
*destination = (byte)(0xe0 | (c >> 12)); ++destination; *destination = (byte)(
0x80 | ((c >> 6) & 0x3f)); ++destination; *destination = (byte)(0x80 | (c & 0x3f
)); ++destination; } } return (int)(destination - bytes); }
[MethodImpl(VersionDifferences.AggressiveInlining)]public static int
GetUtf8Chars(byte* bytes, int length, char* chars) { var destination = chars;
var current = bytes; var end = bytes + length; for (; current < end; ++current)
{if ((*((byte*)destination) = *current) > 0x7f) { return
GetGetUtf8Chars(current, end, destination, chars); }++destination; } return (int
)(destination - chars); } [MethodImpl(MethodImplOptions.NoInlining)] private
static int GetGetUtf8Chars(byte* current, byte* end, char* destination, char*
chars) {if (current + Utf8MaxBytesCount < end) { end -= Utf8MaxBytesCount; //
Unchecked index. for (; current < end; ++current) { var byt = *current; if (byt
<=0x7f) { *destination = (char)byt; } else if (byt <= 0xdf) { *destination = (
char)(((byt & 0x1f) << 6) | (current[1] & 0x3f)); ++current; } else if (byt <=
0xef) { *destination = (char)(((byt & 0xf) << 12) | ((current[1] & 0x3f) << 6)
+ (current[2] & 0x3f)); current += 2; } else { var utf32 = (((byt & 0x7) << 18)
| ((current[1] & 0x3f) << 12) | ((current[2] & 0x3f) << 6) + (current[3] & 0x3f
)) -0x10000; *destination = (char)(0xd800 | (utf32 >> 10)); ++destination;
*destination = (char)(0xdc00 | (utf32 & 0x3ff)); current += 3; } ++destination;
} end+= Utf8MaxBytesCount; } // Checked index. for (; current < end; ++current)
{var byt = *current; if (byt <= 0x7f) { *destination = (char)byt; } else if
(byt <=0xdf && current + 1 < end) { *destination = (char)(((byt & 0x1f) << 6) |
(current[1] & 0x3f)); ++current; } else if (byt <= 0xef && current + 2 < end) {
*destination = (char)(((byt & 0xf) << 12) | ((current[1] & 0x3f) << 6) +
(current[2] & 0x3f)); current += 2; } else if (current + 3 < end) { var utf32 =
(((byt &0x7) << 18) | ((current[1] & 0x3f) << 12) | ((current[2] & 0x3f) << 6)
+ (current[3] & 0x3f)) - 0x10000; *destination = (char)(0xd800 | (utf32 >> 10
)); ++destination; *destination = (char)(0xdc00 | (utf32 & 0x3ff)); current += 3
; }++destination; } return (int)(destination - chars); } public static int
GetUtf8CharsLength(byte* bytes, int length) { int count = 0; for (int i = 0; i
< length; i += bytes[i] <=0x7f ? 1 : bytes[i] <= 0xdf ? 2 : 3) { ++count; }
return count; } public static int GetUtf8MaxBytesLength(int charsLength) {
return charsLength * Utf8MaxBytesCount; }
[MethodImpl(VersionDifferences.AggressiveInlining)]public static int
GetUtf8MaxCharsLength(int bytesLength) { return bytesLength; } }
 

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信