计算机字符编码全攻略

看完本篇博客,会解决你对编码的所有疑问。

首先要搞清楚以下几个概念:

1.字符。字符是各种文字和符号的总称,包括国家文字、标点符号、图形符号、数字等,它是一个信息单位。

2.字符集。字符集是多个字符的集合。字符集种类较多,每个字符集包含的字符个数不同。常见的字符集有:ASCII字符集,GB2312字符集,BIG5字符集,GB18030字符集,Unicode字符集等。在字符集中,有一个码表的存在,每一个字符在各自的字符集中对应着一个唯一的码。但是同一个字符在不同字符集中的码是不一样的。比如字符“中”在Unicode和GB18030中就分别对应着不同的码(20013和54992)。

3.字符编码。计算机要准确的处理各种字符集中的字符,就需要对字符进行编码(即用特定的二进制数值对应该字符或者说将字符在字符集中的对应位置化为二进制),称为字符编码。字符集与字符编码一一对应。(Unicode除外,它有多种编码实现UTF-8,UTF-16,UTF-32等)

4.汉字内码。指计算机汉字系统中使用的二进制字符编码,是沟通输入、输出与系统平台之间的交换码,通过内码可以达到通用和高效率传输文本的目的。

5.内码。char或String在内存里使用的编码方式。

6.外码。除了内码都可以认为是外码。(包括class文件的编码)

 

在这里做下小结:字符相当于我们汉语词典中的汉字,字符编码相当于汉语词典中的拼音(或者某一页,第几个字),字符集相当于汉语词典。

不同的汉语词典,各个汉字的所在页码和在页码中的索引数不同,这就是不同的字符集中,字符编码不同。在这当中有一本词典与众不同,就是Unicode,它是专门为了解决字符集码表不一致而推出的,统一了所有字符对应的码,因此在这个规范下,所有字符对应的码都是一致的(统一码),但是统一码只规定了字符与码表的一一对应关系,却没有规定该如何实现,后面根据这个字符集实现了多种存储方式(存储在硬盘中)包括UTF-8,UTF-16,UTF-32。这些实现就是对Unicode中的字符进行编码,行程了字符与码表直接一一对应的的关系,而UTF-8是Unicode中字符集的一种字符编码实现方式,它规定了字符该如何编码成二进制,存储在计算机中。

计算机字符编码全攻略

这里以Java为例说明内码与外码的转换过程:

Java在内存中都是Unicode编码,不存在什么GBK/UTF-8。只有在输入输出的时候,才会发生于外部的编码转化问题。

Java内码:Unicode(UTF-16)

Jvm默认外码:Windows(GBK),Linux(UTF-8)

[String].getBytes() 就是将内码转换成外码存储在byte数组里,方法有两种形式:

  • getBytes(String charsetName): 使用指定的字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

  • getBytes(): 使用平台的默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

new String(byte[] bytes, String charset): 将字符序列bytes使用“charset”的字符编码(外码),解码成字符串(内码)。

  编码和解码的“字符编码”必须要一致才能解码成想要的字符串(英文例外)。

 

但是为什么在tomcat 下,使用 new String(s.getBytes("iso-8859-1") ,"GBK") 却可以用呢? 答案是:

  tomcat 默认使用iso-8859-1编码, 也就是说,如果原本字符串 str 是GBK的,tomcat传输过程中,将GBK转成iso-8859-1编码的字符数组了,相当于:

String s = "中国";

byte[] str_bytes = s.getBytes("GBK"); // 底层操作1:jvm,此时编码格式为gbk;
String s = new String(str_bytes, "iso-8859-1"); // 底层操作2:tomcat;

String str_new =new String(s.getBytes( "iso-8859-1"), "gbk"); // 得到正确字符串

  默认情况下,使用iso-8859-1读取中文肯定是有问题的,那么我们需要将iso-8859-1 再转成GBK, 而iso-8859-1 是单字节编码的,即他认为一个字节是一个字符, 那么这种转换不会对原来的字节数组做任何改变,因为字节数组本来就是由单个字节组成的,如果之前用GBK编码,那么转成iso-8859-1后编码内容完全没变, 则 s.getBytes("iso-8859-1")  实际上还是原来GBK的编码内容则 new String(s.getBytes("iso-8859-1") ,"GBK")  就可以正确解码了。 所以说这是一种特殊情况。

区位码、国际码与机内码的转换关系(以GB3212字符集为基准):

1.区位码先转换成十六进制数表示

2.(区位码的十六进制表示)+2020H=国际码

3.国际码+8080H=机内码。

举例:以汉字“大”为例,“大”字的机内码为2083

解:1.区号为20,位号为83

2.将区位号2083转换为十六机制表示为1453H

3.1453H+2020H=3473H,得到国际码3473H

4.3473H+8080H=B4F3H,得到机内码B4F3H

小结:字符编码的两种表示方式:ASCII(字符的表示);GB2312(国标码,汉字编码)

 

相关区别:

1.机外码就是外界输入的字符,这个字符输入后会有一个相对应的区位码(就是一个94*94的棋盘格格,你输入的汉字会对应里面的一个格子,行+列就是我们这里的区+位)

2.区位码经过换算可以变成国标码(所以区位码->国标码这一步只是表示转换下而已)

3.国标码再经过换算可以变成机内码(一般是16进制数表示)。

 

这里要记住两点:

1.在计算机内存中统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8(或其他)编码

计算机字符编码全攻略

计算机字符编码全攻略

2.不同的字符集中,字符有不同的字符编码,所以要想判断某个输入的数据是不是某类字符的时候,先确定当前使用的字符集,找到该字符集下该类型字符对应的字符编码范围,即可判断,如判断GB2312下的汉字。

先取得对应的字符编码判断是否是汉字所在的范围即可。具体请参考另外一篇博文

有的人可能还有疑问,这些字符到底存储在哪里,根据搜索到的资料。

系统是不知道某个码点是某个字符的,更不可能知道这个字符长啥样。

系统只是把这个字符告诉终端,终端去字体中找到这个字符对应的图像,再把这个图像显示出来。如果字体中没有,那就会显示方框等所谓的乱码。

emoji的支持也是一样的,所以实际上是字体的功劳。可能系统在emoji的渲染上支持一些额外的特性(比如颜色,或者干脆用图片来代表emoji字符)。

现代操作系统内部都是用Unicode来处理字符的。设定字符集实际上是告诉系统如何处理外码和内码的对应,比如同一个字符,在UTF-8和UTF-16中的编码可能是不同的,字节数量也可能是不同的,但是对应的unicode其实同一个。比如系统处理一个字符,根据设定的字符集找到对应的UTF-8编码输出给终端,但是终端却是用UTF-16的规则来理解这些编码,那自然就会出错了。

这些不同字符集确实需要码表来对应,这些你可以在/use/share/i18n/charmaps下找到。

附上GB2312汉字区范围

参考:

https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896

https://baike.baidu.com/item/%E5%86%85%E7%A0%81/4100505?fr=aladdin

https://www.cnblogs.com/Xieyang-blog/p/9401999.html

https://segmentfault.com/q/1010000019020445

https://cloud.tencent.com/developer/article/1343240