Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

  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 个二进制位表示。例如: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。希望大家支持一下。
最后附上 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;}}

转载于:https://www.cnblogs.com/Dogwei/p/11236706.html

UTF-16 -- 顶级程序员也会忽略的系统编码问题,JDK 错了十年!相关推荐

  1. 2 位谷歌顶级程序员的激荡人生,曾共用 1 台电脑写代码

    (给程序员的那些事加星标) 转自:机器之心(id:almosthuman2014),英文:纽约客 有很多人认为,Jeff Dean 的存在是谷歌如此强大的原因,谷歌员工都把谷歌搜索惊人的速度归功于他, ...

  2. 【漫画】分享16张程序员高端漫画

    来自公众号:顶级程序员 作者:An先生 1.编译中 真的不是我偷懒,程序编译那么久,我真的什么都做不了啊. 2.sudo 三明治 没有什么是一个"sudo"解决不了的问题. 3.新 ...

  3. 嵌入式顶级程序员_3D打印的顶级开放式创新

    嵌入式顶级程序员 开源继续推动3D打印行业的快速创新. 如果您停下来想一想,这很有道理-存在3D打印机可以做其他事情. 将这一理念与免费软件和开源硬件相结合,可以帮助其他人参与改进其制造的对象,并使打 ...

  4. 顶级程序员的心得 Coders at Work (IV)

    ( 第一,第二, 第三部分 ) "Coders at Work",   对15 位顶级程序员的采访, 总共600页. 看似冗长的问答中有不少精辟的言论. 我摘录了一些关于挑选,面试 ...

  5. python在哪里写代码比较适合-程序员面试被要求手写代码,你与顶级程序员的差别在哪?...

    原标题:程序员面试被要求手写代码,你与顶级程序员的差别在哪? 前言: Python现在非常火,语法简单而且功能强大,很多同学都想学Python! 所以小的给各位看官们准备了收藏已久的视频教程分享给大家 ...

  6. 顶级程序员的生活是怎样的? 网友: 很后悔, 找不到女朋友!

    当码农的这几年时间,经历了好几家完全不同类型的公司.具体属于哪些领域范围什么的,这里先不说了 ,身边员工的平均水平已经是很不错的.这种圈子以内,包括自己,基本上没有什么特别明显上等下等之分.但是偶尔会 ...

  7. 顶级程序员和普通程序员在思维模式上的5个区别!

    <The Effective Engineer>的作者在写书的过程中,为了了解那些顶级程序员和普通程序员的区别,采访了很多硅谷顶级科技公司的顶尖软件工程师.他发现这些给世界带来巨大影响的的 ...

  8. [转]《吐血整理》系列-顶级程序员工具集

    你知道的越多,你不知道的越多 点赞再看,养成习惯 GitHub上已经开源 https://github.com/JavaFamily 有一线大厂面试点脑图.个人联系方式,欢迎Star和指教 前言 这期 ...

  9. 顶级程序员的心得ndash;Coders at Work

    [原文在 www.yishan.cc 连载,  现在合成一篇] 我去年读了 "Coders at Work",   对15 位顶级程序员的采访, 总共600页. 从采访的模式看,有 ...

最新文章

  1. LeetCode Insert Delete GetRandom O(1)
  2. sqlsession.selectlist 会返回null么_如何在Java代码中去掉烦人的“!=null”
  3. MFC、OpenCV初探 —— PictureControl中图片的缩放
  4. 合并查找到的文件,至新的文件中
  5. Discuz!X/数据库操作方法
  6. php 生僻字 拼音,php 汉字转拼音 [包含20902个基本汉字+5059生僻字]
  7. 微信公众号 开发详解05【二维码制作、调查表单、短网址、微小宝、引流】
  8. 我的世界服务器物品栏快捷菜单,我的世界:教你几个实用的快捷键小技巧,萌新可能对此一无所知!...
  9. 批量修改pdf文件名称的方法
  10. 简单的python爬虫爬豆瓣图书TOP250
  11. windows下装ipython
  12. 查看获取别人的微信公众号二维码
  13. Catlike Coding Unity教程系列 中文翻译 Basics篇(一)Game Objects and Scripts
  14. 模块递归拆分法: 设计模式 设计原则,复杂层次设计举例。系统重构 装饰模式,门面模式,代理模式
  15. Objective C 常用代码片段制作(code snippet library)
  16. CAD绘图设计中怎样删除CAD图层?怎样清理CAD图层文件?
  17. 零基础学Java语言---编程题
  18. do-while循环
  19. 讲座回顾|2021/4/7|青源美团|CVPR 2021 预讲 · 美团专场,覆盖实例分割,图像分割,表情识别,特征选择和对齐...
  20. 海思视频和QT的Colorkey显示模式

热门文章

  1. 佛系听歌?Beats推出“串珠”耳机 盘它?
  2. Pytorch搭建自己的模型
  3. 从源码角度理解 FragmentTransaction实现
  4. boost1.7 centos7编译
  5. python内置函数next()用来返回文件下一行_Python内置函数 next的具体使用方法
  6. Golang实践录:命令行cobra库实例再三优化
  7. 【java】理解和运用Java中的Lambda
  8. 【clickhouse】clickhouse 主从配置 从节点无数据
  9. 【Spring】Spring Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required
  10. 【ambari】Ambari Rest api 使用