2021.11.22Emoji深入理解一,字符集,字符编码,Unicode,ASCII,UTF-16,大端序小端序

<meta charset="utf-8">

一 疑问

1. 什么是Emoji,跟Unicode什么关系,要搞懂emoji为什么要先理解Unicode?

2. 什么是Unicode,跟ASCII什么关系?

3. 大端序小端序是什么概念?哪些机器用大端序,哪些机器用小端序、

6. 什么是编码?什么是码表?Java用的是什么编码?

二 编码字符集和字符编码表

  1. 编码字符集 (Coded Character Set 即 CCS)

编码字符集的概念就是,给现实世界中的字符,对应的映射一个数字。

这种映射,就是编码字符集。

例如 a=1, b=2, c=3。在计算机里面,将1、2、3分别代表a、b、c;当然这只是我自己一厢情愿的想法,并不会有人遵守这个约定。

现实世界中,最早的字符集是ASCII编码,a=61、b=62、c=63.

在ASCII后面出现的Unicode也属于这一范畴。

  1. 码点

在编码字符集中,字符对应的唯一的编码,称为码点。

例如ASCII中,a的码点就是61,b的码点是62.

  1. 字符编码表 (Charater Encoding Form 即 CEF)

字符编码表的概念是,按照一定的规则,将编码字符集中的码点转换成一定长度的二进制序列。例如UTF-32、UTF-8、UTF-16就是属于这一概念的范畴。相同的字符内容,通过这些不同的编码,在计算机中所占的存储空间是不同的。

  1. 其实在Unicode出现之前,编码字符集和字符编码表是同一个概念,即字符对应的二进制数就是最终计算机中存储的数据。例如ASCII编码就是这样,a的码点是16进制的61,61转为二进制为1100001,在计算机中0110001就代表字符 “a”。

  2. 码元

码元可以理解为:不同的编码方式(CEF),对码点在计算机中存储和计算时的一个最小单位。一个码点可以映射为一个或者多个码元。

三 ASCII

  1. ASCII即 American Standard Code for Information Interchange ,美国信息交换标准代码。就是最早的编码字符集。因为英文字母较少,所以美国科学家用7位比特(bit)来映射128个(2^7)字符。这128个字符中,有33个控制字符例如回车、删除等。其它的都是打印字符。

计算机读写最小单位是字节(Byte),一个字节是8比特(bit),ASCII码只用了7比特,于是ASCII码规定,缺少的1个比特,将最高位用0补齐。这样ASCII既是字符集,也是字符编码表。

[图片上传失败...(image-d5e746-1640270914632)]

  1. 字母“a”的ASCII码是61,这个61是16进制的,转换(在线转换工具https://tool.lu/hexconvert/)为10进制就是97。

在代码中,给char类型的aInt赋值97.会打印出a字符,

public class MainJava {

public static void main(String... args) {

System.out.println("yq JAVA test");

char aInt = 97;

System.out.println("aInt = " + aInt + ", hexString = " + Integer.toHexString(aInt));

}

}

输出结果:

yq JAVA test

aInt = a, hexString = 61

四. Unicode的出现,Unicode和emoji关系。

Unicode就是纯粹的字符集,跟编码方式无关。

  1. 最开始计算机只支持英语,用ASCII码确实够用了。但是随着发展,除了英语外,地球上其他非英语地区也都需要用计算机。于是世界各地都出现了不同的字符集和编码表,比如中国的GBK。

但是这样就出现了不统一的问题,一个地区的字符,按照它们自己的编码规则来映射和存储,到另外一个地区的编码规则来解析,就乱码了。

  1. 于是Unicode应运而生,Unicode就是对世界上所有的字符,都给它一个唯一编码。世界上所有的字符,都有一个唯一的编码,与什么语言、什么平台、什么程序无关。这样字符在传输和解析的时候,就不会乱码了。所以又叫统一码、单一码、万国码。

例如“天”的Unicode码点是22825(十进制,16进制是5929)。

public static void main(String... args) {

System.out.println("yq JAVA test");

char oneChar = '天';

int oneInt = oneChar;

System.out.println("oneChar = " + oneChar + ", oneInt = " + oneInt + ", hexString = " + Integer.toHexString(oneChar));

}

输出:

yq JAVA test

oneChar = 天, oneInt = 22825, hexString = 5929

同时Unicode也给emoji分配了码点,就是一个数字代表一个emoji表情,比如十六进制数字1F600 代表笑脸😀这个emoji表情。

[图片上传失败...(image-90c346-1640270914632)]

  1. Unicode向前兼容ASCII码,承认ASCII占用0 -- 127整数资源的合法性。并在其后接着占用 128 --65535的整数资源,来给世界上所有的字符分配唯一码点。

但是很快Unicode就发现65535远远不够,于是又把后面接着的16个65536整数资源给占用了。所以一共占用了17*65536个整数资源。

  1. Unicode的表示形式是U+后面,跟4个或者6个十六进制数。

例如上面说的最开始的65535个整数,用Unicode的表现形式就是U+0000 -- U+FFFF。 十六进制FFFF对应十进制的65535。

所以Unicode的整个范围,是17个65536的整数资源,也就是U+0000 -- U+10FFFF。

  1. 平面、BMP、SP

Unicode发现65535不够用、又增加了16个65536的整数资源后,Unicode码点的全部范围就是17个65536部分。每一个65536的部分,称为一个平面(Plane)。第一个平面称为Plane0。

Basic Multiligual Pane 即基本多语言平面,就是第一个平面Plane0。范围是U+0000 -- U+FFFF

FFFF转为二进制即:11111111 11111111,需要占用16比特,也就是2字节。

BMP内的U+D800到U+DFFF之间的码位区段,Unicode规定是永久保留的,不映射到具体的字符。这些码点陈为代理码点 Surrogate Code Point。

D800–DBFF属于高代理区(High Surrogate Area),后面的部分DC00–DFFF属于低代理区(Low Surrogate Area),各自的大小均为4×256=1024。

UTF-16就是用这些保留的码点,对辅助平面进行编码,具体的编码规则接下来再讲。

除了基本多语言平面,其它的叫做辅助平面(也叫增补平面) Supplementary Planes。辅助平面的范围是U+010000 -- U+10FFFF,十进制的65536 到 1114111。

  1. 一些辅助平面中,还有很多空间没有分配字符,为了以后扩展。

  2. CJK统一汉字

在BMP中,有一部分码点分配给汉字,称为CJK统一汉字(Chinese、Japanese and Korean中日韩)。这一部分区域的范围是 \u4E00-\u9FA5(19968-40869),这个范围包含了两万多个中文字符。但是这一范围并不能映射所有汉字,所以在增补平面中也有部分生僻汉字。

五 Unicode的编码方式

  1. 一个字符的Unicode码点是确定一致的,但是在为了节省空间的目的、以及不同的平台和系统的编码方式不同,对Unicode的编码方式并不同。

将Unicode码点数字值以某种规则,转换为另一种格式的数字,以便在计算机中储存和传输,就是对Unicode编码。

  1. 为什么要对Unicode编码呢?

电脑中存储和传输的内容都是二进制,Unicode也是以二进制的形式存在于计算机中。1 字节 = 8 比特。

怎么存放这些Unicode码点值,就是对Unicode编码。

Unicode编码称为Unicode Transformation Format,简称UTF。

”华“字的Unicode的编码值是U+534E,转为二进制是 1010011 01001110 一共7位比特,用2个字节表示就可以了(高为补0)。

Emoji表情笑脸“😀”的Unicode编码是U+1F600,转为二进制是 1 11110110 00000000 一共17位比特,并不能用2个字节,至少需要3个字节。

Unicode中码点值的范围是U+0000 -- U+10FFFF,十六进制数 10FFFF 转为二进制是 10000 11111111 11111111 一共21位比特,至少需要3个字节。

Unicode中的有些字符需要2字节,有些字符又需要3字节,怎么在计算机中存储呢?

直接用4个字节(32位比特)来存放Unicode编码,不足的高位直接补0,这就是最直观简单方便的方式了。

UTF-32编码就是这样做的,无论Unicode码是多少,每个字符站4个字节。4字节刚好是32位,这也是UTF-32名称的由来。UTF-32是定长编码。

但是这样会有很多冗余,因为Unicode字符集中,我们常用的字符都在基本平面,范围U+0000 -- U+FFFF只需要2个字节就可以了。

于是就有了UTF-16和UTF-8等编码方式。

六 UTF-16编码

  1. 最开始时UTF-16用2个字节来存储一个字符,2个字节刚好是16位,这也是UTF-16名称的由来。

对于基本平面中的字符,Unicode码点值小于等于FFFF,只需要2个字节就可以了。

Unicode扩充了增补平面后,对于超过FFFF的字符,2个字节就无法存储了,例如Emoji表情笑脸“😀”的Unicode编码是U+1F600。这时就会采用4个字节存储。

所以UTF-16是变长编码。

之前在平面一节有说到,Unicode规定了:基本平面中的 U+D800到U+DFFF是代理区,这些代理区并不存放具体的字符码点。

其中D800到DBFF属于高代理区,后面的部分DC00到DFFF属于低代理区。

如果将这些区域,按某种规则,是否可以存放补充平面中的Unicode码点呢?UTF-16就想到了一个这种规则,具体如下:

  1. 将高代理区和低代理区的排列组合起来,一起来表示一个辅助平面中的码点。其中高代理区D800到DBFF一共有1024种组合(DBFF减D800等于3FF转为十进制1023,再加1)。

低代理区也是一样,有1024种组合,两者合起来,一共是1024 X 1024 = 1048576种组合。

而辅助平面,刚好有1048576个字符码点(U+010000 到 U+10FFFF,也就是65536 到 1114111, 1114111 - 65536 + 1 = 1048576)。这样,代理区就能完全表达辅助平面中的所有字符了。

具体的换算规则(十六进制):

Lead = (码点 - 10000) ÷ 400 + D800

Trail = (码点 - 10000) % 400 + DC00

以Emoji表情笑脸“😀” (Unicode编码是U+1F600)为例:

Lead = (1F600 - 10000) ÷ 400 + D800 = D83D

Trail = (1F600 - 10000) % 400 + DC00 = DE00

所以其UTF-16编码为\uD83D\uDE00。在 https://www.bejson.com/convert/unicode_chinese/ 这个网站中,输入😀 并点击中文转Unicode,得到\ud83d\ude00,证明换算是正确的。

可以在代码中验证:

public static void main(String... args) {

int grinningInt = Integer.parseInt("1F600", 16);

char[] chars = Character.toChars(grinningInt);

for (int i = 0; i < chars.length; i++) {

char grinningChar = chars[i];

System.out.println("grinningChar = " + Integer.toHexString(grinningChar));

}

String grinningStr = new String(chars);

System.out.println("grinningInt = " + grinningInt + ", grinningStr = " + grinningStr);

}

输出:

grinningChar = d83d //高位

grinningChar = de00 //低位

grinningInt = 128512, grinningStr = 😀

  1. 读取时,怎么判断是读取2个字节,还是4个字节呢?

很简单,由于辅助平面中的字符码点值,转化后都是由高代理区和低代理区组成的一对值;每次读取2字节时,如果发现读取的值存在于高代码区则继续再读2个字节,一共读取4个字节。这样就能保证读取正确了。

  1. 许多编程语言内部的编码都是UTF-16,例如java、js、C#、python。

七 UTF-8编码

  1. UTF-16编码虽然是变长编码,一个字符但是最小也要占用2字节。有相当多的字符,只需要一个字节就可以存储了,比如ASCII只占7位,只需要一个字节就可以存储了。

于是有了更精简的编码方式UTF-8。

  1. UTF-8编码规则:

(1).对于单字节字符,字节的第一位设置为0,后面的7位为这个字符的Unicode码。因此对于ASCII码,UTF-8是完全一样的。

(2).对于n字节字符(n>1):第一个字节的前n位都设置为1,第1个字节的第n+1位设置为0。后面的所有字节,开头两位一律设置成10。剩下没有提及的二进制位,从低到高全部用字符的Unicode二进制位补齐。

UTF-8 编码方式 (二进制)

UTF-8 编码方式 (二进制) Unicode 符号范围

0xxxxxxx 0-127

110xxxxx 10xxxxxx 128-2047

1110xxxx 10xxxxxx 10xxxxxx 2048-65535

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-1114111

例如严的 Unicode 是 20005 (4E25)(100 111000 100101),根据上表,可以发现20005处在第三行的范围内,因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101。

对单字符进行转换之后,字符串传输的时候直接拼接即可,切割的时候则先读取第一位的 1 的数量,来判断后面多少字节都是同一个字的,再进行切割。这样,如果中间有漏字符,也可以发现。

比如 “中国”的 UTF-8 表示为:

11100100 10111000 10101101 11100101 10011011 10111101

其实你可以发现,因为 UTF-8 加入了位数提示,所以会占用更多的长度来表达字符串。比如中文通常是 2048-65535 之间,所以一个中文在 UTF-8 会占用 3 个 8 位(3 字节)。而更加节约的 UTF-16 只用占用 2 个字节。但是 UTF-8 可以无误的表达 65535 之后的字符,这是 UTF-16 和 GBK 无法做到的。

在过去的标准里,UTF-8 最多可以用 6 个 8 位(6 字节)表示表示一个字符,然而 Unicode 也只能表示到 1114111,所以 UTF-8 也只需 4 位就足够了。

另外,因为用到 65536 之后的机会并不多。一些数据库 ,比如 Mysql,默认储存 UTF-8 时,就只给每个字符留了最多 3 位的空间。后面 Emoji 兴起后,Mysql 为了兼容之前的版本,不得不新增了一个数据类型 utf8mb4 来支持 4 位的 UTF8,这个功能在 Mysql 5.5.3 中加入。我们应该优先设定 Mysql 数据类型为 utf8mb4 。

八 字节序,大端序,小端序

  1. 字节序,即字节的顺序,是多字节数据在内存中存放的方式。单字节数据当然没有字节序的概念。

字节序分为大端序(Big Endian)和小端序(Little Endian)

小端序:数据的低位字节,存放在内存低位地址。

大端序:数据的低位字节,存放在内存高位地址。

例如有数据168496141,对应的十六进制数是0X0A0B0C0D,在内存地址从左到右依次增加。

大端序 0A0B0C0D

小端序 0D0C0B0A

  1. 大端序符合人类的阅读习惯,所以网络通信中TCP/IP协议规定的是大端序。

而计算机读取时,一般都是从低位内存开始,先后分别读0D,0C,0B,0A效率高。所以计算机内部一般采用小端序。

ARM,Intel等就是用小端序。但也不是绝对,mac IOS就是采用大端序。

还有些机器,同时支持大端序小端序。

  1. 怎么判断机器是哪种端序?摘用网上的一段代码:

include <stdio.h> int main () { unsigned int x = 0x12345678; charc = (char)&x; if(*c == 0x78) { printf("Little endian"); } else{ printf("Big endian"); } return 0; }

九 几个工具网站:

unicode中文互转 https://www.bejson.com/convert/unicode_chinese/

ASCII编码转换 http://www.hiencode.com/cencode.html

进制转换 https://tool.lu/hexconvert/

参考文献

深入理解Emoji(一) —— 字符集,字符集编码 https://www.jianshu.com/p/8d675a5b9e5c

编码问题(上) (说的很详细 Unicode的来源、历史) https://www.cnblogs.com/codefuturedalao/p/14340385.html

字符集与编码(四)——Unicode https://my.oschina.net/goldenshaw/blog/310331

谈谈字符编码:Unicode、UTF-8 和 char[] (最通俗) https://luan.ma/post/character-encoding/

深入分析 Java 中的中文编码问题 https://www.cnblogs.com/bodhitree/p/9035142.html

彻底弄懂 Unicode 编码 https://blog.csdn.net/hezh1994/article/details/78899683

https://www.unicode.org/Public/emoji/14.0/emoji-sequences.txt

Full Emoji List, v14.0 https://unicode.org/emoji/charts/full-emoji-list.html

https://www.unicode.org/Public/emoji/14.0/emoji-test.txt

从Emoji的限制到Unicode编码 那些年的Emoji https://www.jianshu.com/p/64ec0f6b6245

深入理解Emoji(二) —— 字节序和BOM https://www.jianshu.com/p/ca191d9bdcc0

什么是大端序和小端序,为什么要有字节序? https://zhuanlan.zhihu.com/p/352145413

教你用golang判断大小端字节序 https://segmentfault.com/a/1190000039738719

脑残式网络编程入门(九):面试必考,史上最通俗大小端字节序详解 https://cloud.tencent.com/developer/article/1678869

Unicdoe【真正的完整码表】对照表(一) https://blog.csdn.net/hherima/article/details/9045765

【原创】经验分享:一个小小emoji尽然牵扯出来这么多东西?(最能看懂) https://juejin.cn/post/6881336349169811464

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容