文章目录
字节流读取中文会出现乱码(原始的数据跟读取出来的数据两者不一样)。
想要知道乱码的原因,就要用到本节学习的字符集。
但除了字符集以外,还会涉及到之前学习的 计算机存储规则
。
一、前要知识
1)计算机存储规则
在计算机中,任意的数据都是以二进制的形式来存储的。
二进制就是由 0 和 1 这两个数字来组成的,它的计算规则是:逢二进一、借一当二。
其中一个 0
或者一个 1
我们会叫做一个 bit
,中文叫做 比特位
。
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
但是一个 bit
能存储的东西太少了,我们会把 8个bit
分为一组,这样就可以存储 2的8次方
,一共 256
个数据,我们将它称之为一个 字节
,而一个字节是我们计算机中最小的存储单元。
![image-20240503071133140](https://img-blog.csdnimg.cn/img_convert/dc7b94699a933ca77cfc34efb48fc980.png)
计算机在存储英文的时候,只要一个字节就行了,为什么呢?这就跟今天要学习的字符集相关了。
2)字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。
反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(能看懂的) --> 字节(看不懂的)
解码:字节(看不懂的) --> 字符(能看懂的)
字符编码Character Encoding
: 就是一套自然语言的字符与二进制数之间的对应规则。
编码表:生活中文字和计算机中二进制的对应规则
3)字符集
字符集 Charset
:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
- ASCII字符集 :
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
- 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
- ISO-8859-1字符集:
- 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
- ISO-8859-1使用单字节编码,兼容ASCII编码。
- GBxxx字符集:
- GB就是国标(国家标准)的意思,是为了显示中文而设计的一套字符集。
- GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
- GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
- Unicode字符集 :
- Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
- 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
- UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
- 128个US-ASCII字符,只需一个字节编码。
- 拉丁文等字符,需要二个字节编码。
- 大部分常用字(含中文),使用三个字节编码。
- 其他极少使用的Unicode辅助字符,使用四字节编码。
二、ASCII 字符集(英文)
ASCII 就是一个字符集,也叫做 编码表
。在这张表中,它记录有 128个数据
,这 128个数据
对欧洲国家来讲,已经够用了。
在ASCII中,对应的最大数字是 127
,但是它是 0 ~ 127
,因此一共是 128个字符
,而一个字节最多可以表示 256个
数据,因此就可以得出结论:存储英文,一个字节就足以。
假设现在我们要存储小写的 a
,查询 ASCII
后得到数字 97
,二进制是 110 0001
,问:我们难道是直接将这个二进制存储到计算机中吗?
答案:这个二进制还不能直接存,因为计算机中最小的存储单元是1个字节,但是刚刚的 a
的二进制只有 7位
,不足一个字节,因此是不能直接存的。
此时计算机就会编码(将字符集中查询的数据按照一定的规则进行计算,变成真实的,实际能存在硬盘中的二进制数据)。
ASCII
编码规则非常简单:前面补0,补齐8位。
因此真实存储在计算机中的二进制是下面这样子的 0110 0001
,它是以 0
作为开头的。
现在我们要将它从硬盘中读取出来,读到的其实还是这个二进制 0110 0001
。
首先需要进行解码,解码跟编码就是相反的意思:将实际存储在硬盘中的二进制数据按照一定的规则进行计算,变成字符集中对应的数字。
由于在编码的时候,前面补 0
对数据没有什么实际的影响,所以在界面的时候直接转成十进制就行了,得到数字 97
,查询ASCII后,得到小写 a
。
![image-20240503072301883](https://img-blog.csdnimg.cn/img_convert/11087dac2472a4cff26b580ed02731c8.png)
以上就是英文字母在存储和读取的完整过程。
三、那如果是汉字呢?
ASCII里面只有欧洲国家常用的英文字母,是没有中文的,所以你觉得中文想要存储在计算机中,它需要什么呢?
是不是也需要一张类似于 ASCII
的表,让每一个汉字跟唯一的数字产生对应关系,如果没有这个对应关系,中文是存不了计算机的。
在 1981年
发布了 GB 2312
,它的全称为:中华人民共和国国家标准信息交换用汉字编码字符集。
这里的 GB
就是 国家标准
的意思,因为它是 国标
这两个汉字的拼音首字母。
后面的 2312
是一个版本,80
是发布时间,表示是 80年代
发布的。
因此这张表我们也称之为 GB2312字符集
、GB2312编码表
![image-20240503072733425](https://img-blog.csdnimg.cn/img_convert/426e4cf8fae8e06ff08305fbfa98b4d1.png)
1、GB2312编码:1981年5月1日发布的简体中文汉字编码国家标准。收录7445个图形字符,其中包括6763个简体汉字。
过了几年,台湾地区也仿照大陆,自己推出了一个BIG5码表
,在这个码表当中,它收录的都是一些繁体字,要注意的是,在 GB2312
里只有简体中文,是没有繁体的。所以这就出现了一个国家,有两张码表。
2、BIG5编码:也叫作 大5码
台湾地区繁体中文标准字符集,共收录13053个中文字,1984年实施。
但是这可不行,一个国家怎么能有两张码表,都不统一。于是在2000年3月17日推出了一张GBK码表
。
3、GBK编码:2000年3月17日发布,收录21003个汉字,包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编万码中的所有汉字。
这里的 GB
还是 国家标准
的意思,这里的 K
是 扩展
拼音的首字母,也就表示 GBK
是在原有的 2312
的基础上进行的扩展,因此你看它起名字都是有讲究的。
GBK码表
也是目前windows操作系统默认使用的码表。
但是在操作系统中显示的不是 GBK
,而是 ANSI
。因为 Windows操作系统
它有很多的版本,有 简体中文版:默认使用GBK
、繁体中文版:默认使用BIG5
、韩文版:默认使用EUC-KR
、日文版:默认使用Shift-JS
等等,每个版本里面默认的字符集都是不一样的,因此微软没办法,需要兼顾所有,所以给这些字符集起了一个公共的名字:ANSI
。
现在我们要知道,我们用的都是 简体中文版的Windows系统
,ANSI
就表示 GBK
即可。
但是还是有点小不足,因为像一些非洲国家,或者是欧洲国家的文字在这张码表里面还是不包含的,因此在后来,有一个国际组织:美国国家标准协会,它提出了一个 Unicode码表
,这张码表称之为:万国码,号称一万个国家的码表,这里的万只是一个虚词,它就表示在这个码表当中,包含了世界上大多数国家的文字。
4、Unicode编码:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。
正是因为有了这些包含中文的码表,中文才能够在计算机中起飞,而且以后会飞的越来越好,越来越高。
而这么多的字符集中,我们只需要研究其中的两个就行了:GBK
、Unicode
,因为 GBK
是 简体中文Windows操作系统
中默认使用的字符集,而 Unicode
是以后我们工作相关的。
四、GBK
学习一个字符集,其实只要知道英文和汉字是如何利用当前的字符集进行存储的就行了。
因此我们首先来看一看英文如何通过 GBK
进行存储的。
1)计算机的存储规则(英文)(GBK)
如果我们要存储小写的 a
,首先会通过 GBK
查到对应的数字,这个数字也是 97
,因为 GBK
是完全兼容 ASCII字符集
的,在GBK中,英文也是使用一个字节进行存储的,转成二进制就是 110 0001
,同样的,这个二进制也不能直接存,计算机中最小的存储单元是一个字节,因此GBK也需要通过编码的方式将这个数字进行计算,变成真实的,实际能存储在硬盘中的二进制形式。
GBK英文编码的规则跟ASCII是一样的:前面补0,补齐8位。
规则:英文一个字节存储,兼容ASCII,二进制前面补0。
![image-20240503074450592](https://img-blog.csdnimg.cn/img_convert/a37ea61fc6fd1b9d9ddadd5421271d65.png)
2)计算机的存储规则(汉字)(GBK)
① 存储
假设我们现在要存储 汉
这个字,查询GBK后得到数字 47802
,转成二进制是这么一长串 10111010 10111010
,你会发现,太长了。
老外的英文字母是 8
,但汉字是 16
,即两个字节。
两个字节也是有原因的,一个字节最多只能记录 256个
汉字,太少了,因此国家在指定 GBK字符集
的时候它就规定了:一个汉字使用两个字节进行存储,两个字节 16位
,如果全都能用上的话,能记录 2的16次方 655535
个汉字,这就够用了,不能再多了,如果再多用三个字节去存储,就有点浪费空间了。
规则1:汉字两个字节存储。
其中前面的第一个字节叫做 高位字节
,后面的第二个字节叫做 低位字节
。
针对于高位字节还有第二个规则。
规则2:高位字节二进制一定以1开头,转成十进制之后是一个负数。
GBK汉字编码规则:二进制不需要任何变动,直接存储到硬盘中即可。
如果你再将这个数字转成十进制的话,那么第一个字节转成十进制,一定是一个负数;第二个字节转成十进制可能为正,也可能为负。
那么为什么会有这个规定呢?其实这就是为了跟英文区分开。
GBK中英文是一个字节进行存储的,它是兼容ASCII字符集的,在编码的时候二进制前面要补0,因此英文的二进制前面一定是以0开头的。
而中文是两个字节,二进制一定是以1作为开头的,底层的二进制文件也是通过这个规则来区分中文和英文的。
假设以下为GBK编码之后的二进制,在下面这段中,有几个中文,几个英文呢?
![image-20240503080033407](https://img-blog.csdnimg.cn/img_convert/16d050cf900e2d920558a6894f60e7d2.png)
抓住两个核心。
核心1:GK中,一个英文字母一个字节,二进制第一位是0。
核心2:GBK中,一个中文汉字两个字节,二进制第一位是1。
第一个字节,可以发现是以1作为开头的,因此这一定就是中文汉字,中文汉字在GBK中占两个字节,因此前两个字节表示一个汉字,通过解密查表,就可以发现它表示的是 汉
这个字。
再往下,读第三个字节,它是以0作为开头的,因此它是英文,英文在GBK中占一个字节,所以此时我们只需要读一个字节就行了,它就是小写 a
。
![image-20240503081025447](https://img-blog.csdnimg.cn/img_convert/1b191b36a773c7b746e6c2da79e20ca0.png)
同样问题二、问题三也是一样的读法。
② 读取
读取的时候读的肯定还是这个二进制,然后将这个二进制进行解码。
解密就是将实际存储在硬盘中的二进制数据按照一定的规则进行计算,变成字符集上对应的数字。
由于在上面编码的时候没有进行额外的操作,因此对数据没有什么实际的影响。
在解密的时候直接转成十进制就行了,再来查询GBK就是我们想要的汉字。
![image-20240503082644634](https://img-blog.csdnimg.cn/img_convert/8c5a22e475c534a37b8a66907c98661c.png)
五、Unicode
1)引入
我国自己指定了 GBK字符集
,其他国家也是一样的,每个国家几乎都会有自己的字符集。
如果不统一,自己玩自己的,是不利于软件的发展的。
![image-20240503083955276](https://img-blog.csdnimg.cn/img_convert/4268e1cfb45027463648e6d260000117.png)
如果不统一,你就用不了其他国家的软件,因此由美国联合世界各地主要的电脑制造商、软件开发商…组成了一个联盟:统一码联盟(也叫Unicode组织),它们提出了 Unicode字符集
,也叫作 万国码
,号称一万个国家的码表,这里的万只是一个虚词,它就表示在这个码表当中,包含了世界上大多数国家的文字。
![image-20240503084439393](https://img-blog.csdnimg.cn/img_convert/d478564b97b916ad6092093ccda62926.png)
2)计算机的存储规则(英文)(Unicode)
小写的 a
通过 Unicode
查到的数字也是 97
,因为 Unicode
也是完全兼容 ASCII字符集
的。
但是在编码的时候,Unicode
就复杂太多了,它有很多种的编码方案。
最先提出来的是 UTF-16
的编码规则:用2 ~ 4个字节保存。
UTF(Unicode Transfer Format
):Unicode转换格式化的一种方式,后面的 16
表示在这种编码方式中,最常用的就是转成 16个比特位
。
后来又出现了第二种编码方案:UTF-32
编码规则,UTF-32
更加的粗暴,不管你是中文还是英文,统一用 4个字节,32个比特位
进行存储。
不用我说,你也能发现它们的不好。
UTF-16
对于 abcd
这样的英文字母不是很友好,明明一个字节就能搞定的事,为什么要用两个字节,太浪费空间了。
UTF-32
更夸张了,统一使用 32个比特位
进行保存,空间过于浪费。
![image-20240503085332686](https://img-blog.csdnimg.cn/img_convert/5e0c8a9415be0b0e7ffdbc73ec307b3d.png)
所以这是不好的行为,因此就出现了 UTF-8
的编码规则,它是针对 Unicode字符集
的一种可变长度字符编码,转成二进制后,它是用 1 ~ 4
个字节进行保存的,这种编码规则也是我们现在在实际开发中最为常见的,因此我们需要重点的去学习它。
-
如果是
ASCII
里面的英文字母,统一使用1个字节表示。 -
如果是
拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文
,统一使用两个字节表示。 -
如果是
中日韩文字、东南亚文字、中东文字
统一使用三个字节表示。 -
如果是
其他语言
,就使用4个字节
表示。
![image-20240503090014349](https://img-blog.csdnimg.cn/img_convert/af8a2c39330f5cfc6157a7599147ee89.png)
这么多的规则我们只需要知道两点就行了。
![image-20240503090034211](https://img-blog.csdnimg.cn/img_convert/a1e50225175f5b8af4db459d3972e26f.png)
在编码的时候也是有规则的。
-
如果是
1个字节
,前面直接补0即可 -
如果是
3个字节
,左边的第一个字节左边要加1110
,中间的第二个字节前面要加10
,右边的第三个字节前面要加10
。剩下来的
xxx
就使用Unicode
中查询到数字的二进制进行填补。
![image-20240503090508800](https://img-blog.csdnimg.cn/img_convert/a7d3d714b6eb50f482a0fb6ac6d63864.png)
我们来举个例子,因为时间上所有的字符集实际上都是兼容 ASCII
的,所以小写的 a
在 Unicode
中查询到的数字也是 97
。
转换成二进制是 110 0001
,又因为 UTF-8编码
规定,ASCII
的英文是采取一个字节进行存储的,所以我们需要来看上面一个字节的转换方案,直接补0即可,后面的 x
就用 97
的二进制进行填补,最终的结果是 0110 0001
。
3)计算机的存储规则(中文)(Unicode)
接下来看中文,假设要存储 汉
,查询完 Unicode
后得到的数字:27721
,转成二进制是这么一长串 01101100 01001001
。
但是这个二进制不能直接存,需要进行 UTF-8编码
,由于中文是使用三个字节存储的,此时就需要来查看三个字节的编码规则。
左边的第一个字节左边要加 1110
,中间的第二个字节前面要加 10
,右边的第三个字节前面要加 10
。剩下来的 xxx
就使用 Unicode
中查询到数字的二进制进行填补。
因此最终得到的结果是这样的一串二进制数据,红色的是 UTF-8编码规定
需要添加的,而黑色的就是用前面一串二进制进行填补得到的结果。
通过这些,需要记住一个结论:在 UTF-8
的编码规则下,英文一个字节;中文三个字节,而且中文第一个字节的首位一定是 1
。
3)练习
1、UTF-8
是一个字符集吗?
UTF-8
不是字符集,它是 Unicode字符集
的一种编码方式
2、如下图
PS:以1开头的一定是中文,而中文在UTF-8编码规则
中,它是占三个字节的,因此我们需要一次获取三个字节才能得到一个中文。
六、总结
1、在计算机中,任意数据都是以二进制的形式来存储的
2、计算机中最小的存储单元是一个字节
3、ASCII字符集中,一个英文占一个字节
4、简体中文版Windows,默认使用GBK字符集
5、GBK字符集完全兼容ASCII字符集
- 一个英文占一个字节,二进制第一位是0
- 一个中文占两个字节,二进制高位字节的第一位是1,第一个字节转成十进制是负数;第二个字节转成十进制可能为正,也可能为负。
6、Unicode字符集
的UTF-8编码格式
- 一个英文占一个字节,二进制第一位是0,转成十进制是正数
- 一个英文占三个字节,二进制第一位是1,第一个字节转成十进制是负数