计算机字符编码

一、计算机字符编码

理论(以下内容仅针对计算机软件领域)

字节(Byte):计算机信息技术用于计量存储容量的一种计量单位,不分数据类型,一个字节代表8个比特位(Bit),即0、1自由组合组成的8位二进制串
字符:可以理解字符是站在人的角度去看待万物,任何一个文字或符号都是字符。字符是各种文字和符号的统称,包括各国家文字、标点符号、图形符号、数字等,即一个标点符号(!)是一个字符,一个文字(汉)也是一个字符
字符集:为了在计算机中表示字符,每一个字符都有一个唯一的编号,即每一个字符和一个唯一的编号存在一对一的映射关系,所有的字符与编号之间这种映射规则的集合称为字符集
字符编码: 字符编码就是根据相应的字符集编号以一定的规则编码成二进制形式存储,相应的将二进制形式还原成字符的操作叫做字符解码,可以简单理解大部分场景下的乱码现象都是源于编码和解码方式的不一致,就像我用英文给你写了一封信,你用中文去读(解码),于是这封信在你看来就是所谓的乱码

二、发展史

第一阶段(起源)

ASCII编码

        计算机是美国发明的,在计算机的世界里只有0和1,我们平常看到的字符文字也就只能用0和1来表示,于是美国科学家们为了表示实际中的事物,制定了一套编码标准,即ASCII编码,占1个字节,首字节用作奇偶校验位,有效值为7位(0~127)(ASCII编码全表:链接

字节数二进制编码格式取值范围
单字节0XXXXXXX0~127

规则:
0-31及127(共33个)为控制字符或通信专用字符,如控制符:LF(换行)、CR(回车)等,
32-126(共95个)是字符,其中48-57为十个阿拉伯数字,65-90为26个大写英文字母,97-122为26个小写英文字母,其他是一些运算符号、标点符号等

第二阶段(诸子百家)

ISO-8859-1(Latin1)编码

        计算机传播到了欧洲,欧洲人发现自己的符号文字没有被编进去,于是欧洲科学家讨论后决定既然美国人将首字节第一位留下来那我们就使用128~255的位置好了(ISO-8859-1编码全表:链接

字节数二进制编码格式取值范围
单字节0XXXXXXX0~127(兼容ASCII编码)
单字节1XXXXXXX128~255

规则:
0-127位置不变兼容ASCII编码,128-159为控制字符,160-255为文字符号,包含了西欧语言、阿拉伯语、希腊语等,刚好用完每一个位置,世界真美好

GB系列
    GB2312编码

    计算机传播到中国,咱们的科学家发现美国人制定的这套编码一共才一个字节,最多0-256的空间,而我们的汉字字数远远超过256,想想我们那厚重的中华字典,于是聪明的中国科学家决定使用2个字节表示汉字,否则真的难搞,美国的ASCIII编码0~127保留下来,其他乱七八糟欧洲国家什么的编码全部删除,对于英文和西欧字符使用一个字节表示即可,对于汉字字符,使用2个字节表示,这就是GB2312编码,它收录了6763个汉字以及682个特殊符号,已经囊括了生活中最常用的所有汉字,不包括一些繁体字,但是对于日常使用已经足够((GB2312编码全表:链接
|字节数|二进制编码格式
|–|–|–|
|单字节|0XXXXXX(兼容ASCII编码)
|双字节|1XXXXXXX 1XXXXXXX

规则:
如果是英文字母,用一个字节表示,最高位为0,兼容ASCII编码,
如果是汉字,用两个字节表示,两个字节最高位都为1,高位字节表示范围:0xA1-0xF7,低位字节表示范围:0xA1~0xFE(预留了很多空间)

    GBK编码

    由于GB2312只有6763个汉字,而我们的中文博大精深,于是在保证兼容ASCII编码、GB2312编码前提下,也用两个字节表示一个汉字的方式编码了很多汉字,经过扩充后,可以表示的汉字达到了20902个,另外有984个汉语标点符号、部首等,这就是GBK编码(GBK编码全表:链接
|字节数|二进制编码格式
|–|–|–|
|单字节|0XXXXXX(兼容ASCII编码)
|双字节|1XXXXXXX XXXXXXXX

规则:
如果是英文字母,用一个字节表示,最高位为0,兼容ASCII编码,
如果是汉字,用两个字节表示,第一个字节最高位为1,高位字节表示范围:0x81~0xFE,低位字节表示范围:0x40-0x7E和0x80-0xFE

    GB18030编码

    后来GBK的两万多字也无法满足我们的需求了,为了照顾日韩和少数民族,2字节表示一个字已经不够用了,我们又对GBK加以扩充,因此GB18030使用变长编码,多出来的汉字使用4个字节表示。(GB18030编码全表:链接
|字节数|二进制编码格式
|–|–|–|
|单字节|0XXXXXX(兼容ASCII编码)
|双字节|1XXXXXXX XXXXXXXX
|四字节|1XXXXXXX 0011XXXX 1XXXXXXX 0011XXXX

规则:
如果是英文字母,用一个字节表示,最高位为0,兼容ASCII编码,
如果是汉字,两个字节表示的字符和GBK一样,用4个字节表示的字符,第一个字节表示范围:0x81-0xFE,第二个字节表示范围:0x30-0x39,第三个字节表示范围:0x81-0xFE,第四个字节表示范围:0x30-0x39(每次解析数据时根据这个字符的第二个字节判断范围是否在0x30-0x39之间,如果存在则说明这是四个字节表示的字符,否则说明是两个字节表示的范围,可以看上面GBK低位字节表示范围是大于0x39的,这就是GBK低位字节0x30-0x39不编码的原因)

。。。。上面仅介绍了美国人的编码标准、欧洲人的编码标准、中国人的编码标准,这些只是冰山一角,世界上存在着各种各样的编码标准

第三阶段(统一)

Unicode

    每个国家都按照自己的标准制定一套编码规则字符,就会导致两个编码不一致的国家的人相互之间无法交流的问题(除了美国可以和任意的国家无障碍交流,因为大部分编码标准都兼容ASCII),有没有一种标准可以收录世界上所有的字符,并提供存储实现呢?
Unicode应运而生,它编排了世界上几乎所有的字符,总共收录近110 多万个字符集合,编号范围从 0x000000 到 0x10FFFF,Unicode是一种编码标准,只是为世界上所有的符号进行了收录编号,并没有指定每个字符每个编号该如何映射为某个二进制串,于是它有几个具体的实现:

UTF-8编码
字节数Unicode 十六进制编码范围二进制编码格式
单字节0000 0000 - 0000 007F0XXXXXXX(兼容ASCII编码)
双字节0000 0080 - 0000 07FF110XXXXX 10XXXXXX
三字节0000 0800 - 0000 FFFF1110XXXX 10XXXXXX 10XXXXXX
四字节0001 0000 - 0010 FFFF11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
五字节0020 0000 - 03FF FFFF111110XX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX
六字节0400 0000 - 7FFF FFFF1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX

规则:
对于单字节(编号小于 127) 的字符来说,UTF-8 编码标准等同于 ASCII编码标准,字节首位设为0,后7位为符号的Unicode码
对于其他n字节的字符(n>1),第一个字节的前n位都设为1,n+1位设为0,后面字节的前两位一律设为10

以汉字“中”为例:
        编码时:“中”的的Unicode是0x4E2D(100111000101101),对照上面可知0x4E2D在Unicode十六进制编码范围0000 0800 - 0000 FFFF之间,即占3个字节,编码形式为1110XXXX 10XXXXXX 10XXXXXX,填充方式就是将“中”的二进制从后向前填充,多余的X使用0填充,那么“中”的UTF-8编码就是11100100 10111000 10101101,转换为十六进制为0xE4B8AD;
        解码时:根据UTF-8编码规则进行解码,判断首字节是0的话,那么这个字节就对应一个字符(单字节),首字节前两位是11的话,那么就代表2个字节代表一个字符(首字节前n位1的个数代表字符占用字节数),如“中”的UTF-8编码是E4B8AD(11100100 10111000 10101101),首字节有3个1,表示是3个字节代表一个字符,然后根据规则1110xxxx 10xxxxxx 10xxxxxx获取到x的部分,即100111000101101,转换为十六进制为0x4e2d,查找Unicode字符集中对应的汉字“中”

UTF-16编码
字节数Unicode十六进制编码范围二进制编码格式Unicode字符范围
双字节0x0000-0xFFFFXXXXXXXX XXXXXXXX0-65535
四字节0x10000-0x10FFFF110110YYYYYYYYYY 110111XXXXXXXXXX65536-1114111

规则:
    使用定长2个字节或2个代理对(4个字节)编码Unicode字符。Unicode将字符分成了17个组(区)编排,每一个组又称为一个平面,每个平面有65536个码位即每个平面能容纳65536个字符,第一组又称为基本平面(BMP),编码范围为0x0000-0xFFFF,所有常见的字符和文字都在这个平面内,其他的组或区叫辅助平面,编码范围是0x10000-0x10FFFF。
    基本平面内有个被称为代理区的特殊区域,编码范围是0xD800-0xDFFF,共有2048个码位,此区间不对应任何字符,主要是用于映射辅助平面的字符,这段区域分为两部分:110110XX XXXXXXXX(0xD800 - 0xDBFF)为高位代理(High Surrogate),110111XX XXXXXXXX(0xDC00 - 0xDFFF) 为低位代理(Low Surrogate)。(所谓代理就是一种特殊的字符,别被名字迷惑)。
    一个高位代理和一个低位代理可以组成一个代理对,如果Y和X全为0,则为0x010000 的代码点,全为1则为0x10ffff 的代码点,正好将辅助平面全部编码
    UTF-16编码的规则是:基本平面内的字符用两个字节来表示,其他辅助平面的字符用4个字节来表示:
    如果Unicode码位位于 0x00 - 0x00FFFF,直接进行二进制编码,位数不够的左边充 0;
    如果代码点位于 0x010000 - 0x10FFFF,则:
        1、代码点减去 0x10000,会得到一个位于 0x00 和 0x0FFFFF 之间的数字
        2、这个数字转换为 12 位二进制数,位数不够的,左边充 0,记作:YYYYYYYY YYXX XXXXXXXX
        3、取出 YYYYYYYY YY,并加上 11011000 00000000(0xD800),得到高位代理
        4、取出 XX XXXXXXXX,并加上 11011100 00000000(0xDC00),得到低位代理
        5、高位代理和低位代理相连,得到 110110YY YYYYYYYY 110111XX XXXXXXXX

以汉字“中”为例:
        编码时:“中”的Unicode是0x4E2D(100111000101101),对照上面可知0x4E2D在Unicode十六进制编码范围0000 0800 - 0000 FFFF之间,即占2个字节,直接进行二进制编码,位数不够的左边补0,那么“中”的UTF-16编码就是01001110 00101101,即0x4E2D

以汉字“虫”为例(这个字不是“虫”,少个腿,只不过CSDN这里面输入有问题,看图),
在这里插入图片描述
        编码时:“虫”的Unicode是0x20010,对照上面可知0x20010在Unicode十六进制编码范围0x10000-0x10FFFF之间,即占4个字节,使用2个代理对表示,Unicode码位0x20010减去0x10000,会得到数字0x10010,将这个数字转换为12位二进制数,位数不够的左边补0,即0001 0000 0000 0001 0000,取出前10位(0001 0000 00)并加上11011000 00000000(0xD800),得到高位代理0xD840(1101 1000 0100 0000),取出后10位(00 0001 0000)并加上 11011100 00000000(0xDC00),得到低位代理0xDC10(1101 1100 0001 0000),将高位代理和低位代理相连,得到0xD840 DC10
        解码就是将上述步骤反过来解析,如果解析时代理不成对,则计算机不显示该代理字符

UTF-32编码
字节数Unicode十六进制编码范围二进制编码格式Unicode字符范围
四字节0x0000-0x10FFFFXXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX0-1114111

规则:使用4个字节存储,直接进行二进制编码,位数不够左边补0;

以汉字“中”为例:
        编码时:“中”的Unicode是0x4E2D(100111000101101),直接转换为二进制补0 为00000000 00000000 01001110 00101101

注:这3种编码方式都是对Unicode标准的具体字符编码方案实现,排版顺序不代表时间先后顺序

总结:几种常见编码兼容性如图
在这里插入图片描述

	ASCII被几乎所有编码兼容(UTF-16、32除外)。最常见的UTF-8与GBK之间除了ASCII部分之外没有交集,这也是平时业务中最常见的导致乱码场景,而GB系列中,GB18030兼容GBK,GBK又兼容GB2312;
	UTF-8:兼容ASCII编码、不存在字节序问题、变长

三、相关概念

字节序(BOM、大端、小端)

        字节序指多字节数据在计算机内存中存储或者网络传输时字节的顺序,字节序有两种:大端BigEndian(高位字节在低内存地址), 小端LittleEndia(低位字节在低内存地址)。CPU处理多字节数有不同方式:例如“汉”字的Unicode编码是6C49。大端 6C49 (将高序字节存储在起始地址),小端 496C (将低序字节存储在起始地址),如图所示
在这里插入图片描述
注意:UTF-8以单个字节为编码单元,CPU处理处理也是以单字节处理,不存在字节序的问题,但也可以用BOM来表示编码方式(UTF-8编码的BOM是EF BB BF,这样当接收者受到以EF BB BF开头的字节流就知道是UTF-8编码了 ),UTF-16以两个字节为编码单元,CPU在解释一个UTF-16文本前需要知道它的字节序,例如,“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59,当我们接收到UTF-16字节流“594E”时,如何判断是“奎”还是“乙”?这时候就必须需要用到BOM

UTF-编码BOM
UTF-8 without BOM
UTF-8 with BOMEF BB BF
UTF-16 LEFF FE
UTF-16 BEFE FF
UTF-32 LEFF FE 00 00
UTF-32 BE00 00 FE FF
应用场景:
        网络序:即网络字节序,都是大端模式(TCP/IP协议对各层协议统一规定使用大端模式,也就是先接收到的字节为数据的高位,存放到低地址)
        主机序:即机器的字节序,有大端模式也有小端模式,根据具体机器CPU处理器决定(计算机内部的数据是不变的,区别只是在于CPU处理数据的方式不同,x86和一般的OS(windows,Linux)使用的是小端模式,Mac OS是大端模式)

ANSI编码

        准确说,并不存在某种具体的编码方式叫做ANSI,它只是Window操作系统上的一个别称,在中文操作系统上ANSI就是GBK编码,在韩语操作系统上ANSI就是EUC-KR(一种韩语编码),并且所谓的ANSI只存在于Windows操作系统上

内码/外码

        内码简单理解就是某种语言运行时,其char或string在内存中的编码方式。java中的内码使用的是UTF-16编码,相对于内码而言其他的编码都是外码

四、FAQ

1、给定一个文件,如何用Java程序检测是什么编码?

答:只有一个可行方法就是推测文件编码方式,所有的方法都不能保证推测出来的结果是绝对准确的,有的方法推测的准确率较高,而有的方法推测出来的准确率较低,主要有以下几种:

  • 通过文件的前三个字节判断:因为有些编码格式会存在文件的前三个字节中,比如UTF-8编码格式文件的前三位为-17、-69、-65,很明显,这种方式局限性比较大且推测的准确率也较低;
  • 通过特殊字符来判断:通过比对某些编码格式文件中出现的一些特殊字节值来推测,同样准确率也不高
  • 通过cpdetector工具库来判断:cpdector 是一款开源的文档编码检测工具,可以检测
    xml,html文档编码类型,基于统计学原理来推测文件编码,但是也不保证结果准确性
  • 通过ICU4J库来判断:ICU的推测逻辑基于IBM过去几十年收集的字符集数据,理论上也是基于统计学的。这种方式统计的结果准确性也较高推荐使用。

2、指定一个记事本文件存储,打开文件的时候解码是如何判断是什么编码的?

答:记事本的做法是在TXT文件的最前面保存一个标签,如果记事本打开一个TXT,发现这个标签,就说明是unicode。标签叫BOM,如果是0xFF 0xFE,是UTF16LE,如果是0xFE 0xFF则UTF16BE,如果是0xEF 0xBB 0xBF,则是UTF-8。如果没有这三个东西,那么就用ANSI(即记事本的默认编码字符集),即使用操作系统的默认语言编码来解释,所以当我们保存记事本文件以其他编码如台湾繁体编码BIG5时,再次打开记事本文件,会发现乱码。

3、为什么计算机最小的存储单位是字节?最小的传输单位是bit?

答:一个位bit代表0或1(即二进制),每8个位代表一个字节byte,
        byte字节是电脑中表示信息含义的最小单位,因为一个位(即0或1)并不能表示我们现实生活中的一个相对完整的信息,且内存中运算的最小存储单位是字节,位运算就是在一个字节的存储单元基础上进行的,所以可以理解字节是存储的最小单位
        bit位是二进制数的一位包含的信息或2个选项中特别指定1个的需要信息量称为一比特,只有两种状态:0和1。电脑内部的电路工作有高电平和低电平两种状态.所以就用二进制来表示信号,以便计算机识别。所以计算机能传输的最小单位当然是你信号的单位bit,而不是字节。

4、当CPU从内存中读取到0101这样的一串数据时,CPU如何辨别它是数字01234…还是对应ASCII码表的某个数字呢?

答:CPU层面是没有ASCII的概念,一切都是指令码和算术逻辑数据。C语言等高级语言才有数值和字符的概念区分,经过编译后得到的机器码已经没有字符的概念。
举个栗子:a='1’编译后就是在a的位置上存上1的ASCII码对应的二进制数,b=1编译后就是在b的位置上存上数值1对应的二进制数,后续程序对a、b的操作可能是a = a + 1,b = b * 2 (在a的位置上存入a的后一个字符’2’的ASCII码对应的二进制数,在b的位置上存入b的二倍即2对应的二进制数),程序编译后CPU并不知道也不需要知道你存的二进制数代表对应的ASCII码值还是数值,只是机械运算即可,结果意义由具体程序的代码逻辑进行保证 ,正常编程逻辑是不可能出现a= a * 2,因为含义不明,如果这个语句逻辑编译通过,意味着CPU需要将a的ASCII码值当做数字乘以2,结果意义同样由程序的代码逻辑进行保证,比如板砖既可以用来盖建大厦也可以用来拍人,具体使用是由在不同场景下使用者(程序)决定的。

5、计算机中二进制0和1能表示数字,为什么还用ASCII码表示,不冲突吗?

答:不冲突。计算机程序中数值和字符是两种不同类型的数据,ASCII码是字符编码集,是计算机用于表示和显示字符的,他的真值是对应字符的映射的八位二进制编码。计算机的显示一个数值并不是直接从他的数值来的,而是要将这个数值转换为对应的字符再显示。数值1 != 字符’1’,就像数学的1和语文的一是不一样的。

6、为什么要有字符编码?字符编码需要做什么事情?

答:计算机内所有信息都是以二进制形式表示的,对于一个短路来说,0代表关,1代表开。那把这些电路组合起来就可以有长串0和1组成的二进制编号,我们就能用它来表示我们想要表示的东西了。但是我们人类不适合直接看二进制(那么多的0、1),所以就需要一种方法将二进制转换为我们能看懂的东西,字符编码就应运而生,你可以想一下翻译机的作用。
        字符编码主要做两件事:给所有要表示的字符一个唯一的编号,即一个编号对应一个字符的映射关系(字符集);将这个编号用0和1表示出来(说明下,有时候并不是直接将编号用二进制表示出来那么简单,还需要考虑读取时字符如何分隔等,例如"喜"对应的字符集编号为1(00000001,1个字节),"欢"对应的字符编号为2(00000010,1个字节)中文汉字那么多,一定还有一个汉字被编号为258(00000001 00000010),那计算机在读到00000001 00000010这一串二进制2,会解析为"喜欢"两个字还是258代表的那一个字呢,就这体现了字符编码的规则)。

7、为什么UTF-8没有字节序,UTF-16、UTF-32有字节序?为什么GBK也没有字节序问题?

答:编码单元与编码单元之间在存储和传输中的顺序是确定的,即字节序和是否多字节编码无关,如a、b、c分别代表3个字节,发送时顺序是abc,那么接收时也会是abc,这个顺序不会乱;
        字节序指的是编码单元内部的字节顺序:UTF-8是变长编码,编码单元为单字节,不存在高低位的问题,且UTF-8的首字节记录了总字节数,保证了解码的正确,也就不存在字节序问题;而以UTF-16为例,编码单元为2个字节,也就是最2个字节代表一个字符,每个编码单元与编码单元之间顺序是确定的,问题在于编码单元内部这两个字节的顺序不确定,由于硬件CPU的不同差异,假如CPU是大端序则高位在低内存地址位,假如CPU是小端序则低位在低内存地址位,所以为了区分有了BOM,这样计算机就可以根据BOM知道高位是谁,低位是谁,知道高低位从而就能正确组装数据保证解码正确;GBK需要参照区位表,将字符转化为字节数组,也属于单字节编码,不需要考虑字节序问题。

8、网路通信时是否需要进行字节序转换?

答:网络协议统一规定使用大端模式,即接收到第一个字节是高字节,存放到低地址位上,所以在发送数据时就会去低地址位取数据的高字节。

  • 大端->大端:不需要转换字节序(发送方网络协议函数去低地址位取高字节数据,网络传输协议是大端模式,接收方网络协议函数会将接收的第一个字节放到低地址位存储,双方正确收发数据)
  • 大端->小端:需要转换字节序(发送方、网络传输没有问题,接收方接收存储数据(高字节存储到低地址位,即按照大端模式存储),提取或解析数据使用本地主机序(小端模式),肯定会出现错误)
  • 小端->大端:需要转化字节序(发送方网络协议去低地址位取高字节数据(实际取到的是低字节数据),传输数据,接收方接收存储数据(接收到第一个字节放到低地址位上,即按照本地主机序大端模式存储,但第一个字节实际上是低字节数据,也就是将低字节数据存储到了低地址位),提取或解析数据使用本地主机序(大端模式),肯定会错误)
  • 小端->小端:不需要转换字节序(发送方网络协议去低地址位取高字节数据(实际取到的是低字节数据),传输数据,接收方接收存储数据(接收到第一个字节放到低地址位上,即按照本地主机序小端端模式存储,但第一个字节实际上是低字节数据,也就是将低字节数据存储到了低地址位),提取或解析数据使用本地主机序(小端端模式),双方正确收发数据)

结论:相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。

9、Java如何判断当前平台字节序?

答:通过 ByteOrder.nativeOrder() 方法
在这里插入图片描述
查看源码可知主要实现为static静态代码块,首先为long分配8个字节内存,然后为long分配值,随后获取该值的第一个字节,如果为高位则平台采用的大端字节序,如果为低位则采用的小端字节序
在这里插入图片描述

五、参考资料

https://zh.wikipedia.org/wiki/ASCII
https://zh.wikipedia.org/wiki/ISO/IEC_8859-1
https://zh.wikipedia.org/wiki/GB_2312 https://zh.wikipedia.org/wiki/%E6%B1%89%E5%AD%97%E5%86%85%E7%A0%81%E6%89%A9%E5%B1%95%E8%A7%84%E8%8C%83
https://zh.wikipedia.org/wiki/GB_18030
https://zh.wikipedia.org/wiki/Unicode
https://zh.wikipedia.org/wiki/UTF-8
https://zh.wikipedia.org/wiki/UTF-16
https://zh.wikipedia.org/wiki/UTF-32
https://www.jianshu.com/p/1b00ca07b003

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值