本文大致介绍了三个问题:
- 常见的字符编码以及他们是如何编码从而被计算机识别的?
- 为什么会有这些字符编码和他们被创建的背景和顺序?
- 常见的乱码问题应该如何防止以及如何解决?
常见的字符编码
ASCII,GB2312,GBK,Unicode,IOS-8859-1
字符集和字符编码的区别
字符集:多个字符的集合
字符编码:将字符集中的字符映射为特定的字节或者字节序列,它表示的是一种规则。
通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码,如: ASCII、ISO-8859-1、GB2312、GBK都是表示了字符集又表示了对应的字符编码,但Unicode字符集是特例, 它对应的字符编码有: UTF-8、UTF-16、UTF-32)
字符编码产生的原因
在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,
而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则, 于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。
ASCII 编码
如图显示了ASCII字符集中所有字符对应的二进制数
ASCII(American Standard Code for information interchange):美国信息交换标准代码,包括了英文、符号等。
标准ASCII使用1个字节存储一个字符,首位是0,总共可表示128个字符。
因为所有ASCII字符集一共只有128个, 2^7=128, 占不够8位,而计算机中一个字节最少是8位,所以就在最前面补一个0
标准的ASCII字符集 ,对美国佬来说完全够用,对中国人来说不够。
国标码
GB2312-80 标准
GB2312-80 是 1980 年制定的中国汉字编码国家标准。共收录 7445 个字符,其中汉字 6763 个。
GB2312 兼容标准 ASCII码,采用扩展 ASCII 码的编码空间进行编码,一个汉字占用两个字节,每个字节的最高位为 1。
GBK(汉字内码扩展规范,国标码)
《汉字内码扩展规范》(GBK) 于1995年制定,兼容GB2312、GB13000-1、BIG5 编码中的所有汉字,使用双字节编码,编码空间为 0x8140~0xFEFE,共有 23940 个码位,其中 GBK1 区和 GBK2 区也是 GB2312 的编码范围。收录了 21003 个汉字。
GBK向下与 GB 2312 编码兼容,向上支持 ISO 10646.1国际标准,是前者向后者过渡过程中的一个承上启下的产物。
举例说明GBK是如何编码的
那么 我a你 这3个字符就是这样存储的,中文还是两个字节,a 是一个字节,那计算机怎么分辨取那两段呢?
xxxxxxxx xxxxxxxx|0xxxxxxx|xxxxxxxx xxxxxxxx
GBK规定:汉字的第一个字节的第一位必须是1
就是这样了:
1xxxxxxx xxxxxxxx|0xxxxxxx|1xxxxxxx xxxxxxxx
拿到Java程序里打印出来就是这样的:
//按照指定字符集进行编码
String data = "a我b";
System.out.println(Arrays.toString(data.getBytes("GBK")));
//输出:[97, -50, -46, 98] 四个字节
所以16位真正用到的就15位,那么2^15等于多少就可以表示多少个汉字了
GB18030 编码标准
国家标准GB18030-2000《信息交换用汉字编码字符集基本集的补充》是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。
GB18030-2000编码标准是由信息产业部和国家质量技术监督局在2000年 3月17日联合发布的,并且将作为一项国家标准在2001年的1月正式强制执行。
GB18030-2005《信息技术中文编码字符集》是我国制订的以汉字为主并包含多种我国少数民族文字(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集强制性标准,其中收入汉字70000余个。
以上就是国标码,但是全世界如此多的国家,字符形式也是五花八门,必然不能满足需求。所以就有了统一码(Unicode)。
统一码(Unicode)
统一码(Unicode),也叫万国码、单一码,由统一码联盟开发,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。
统一码是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
统一码是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。统一码用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。
UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
编码方案
这里以 UTF-8 编码 举例:
UTF-8:是 Unicode 字符集的一种编码方案,采取可变长编码方案,共分为四个长度区:1、2、3、4个字节,英文字符、数字等只占 1 个字节(兼容标准 ASCII 编码),汉字字符占用 3 个字节
来看 a我m 对应的字节
字符 | 码点 | 字节 |
---|---|---|
a | 97 | 01100001 |
我 | 25105 | 110 001000 010001 |
m | 109 | 01101101 |
UTF-8编码规定:
- 0xxxxxxxx (ASCII码,也就是1个字节首位默认是0)
- 110xxxxx 10xxxxxx (2个字节编码,第一个字节前面默认110,第二个字节前面默认10)
- 1110xxxx 10xxxxxx 10xxxxxx (同上,所以 我 这个字符 编码就是 11100110 10001000 10010001)
- 11111xxx 10xxxxxx 10xxxxxx 10xxxxxx
通过这样就可以区分出来解码了,所以字符编码时所使用的字符集,和解码时所使用的字符集必须一致,否则会出现乱码。
英文、数字一般不会乱码,因为很多字符集都兼容了ASCII编码。
ISO-8859-1 编码
ISO-8859-1编码是单字节编码,向下兼容ASCII。 Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。
因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。
BOM
BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码。
比如UTF-8:UTF-8 不需要 BOM 来表明字节顺序,但可以用 BOM 来表明编码方式。字符 “Zero Width No-Break Space” 的 UTF-8 编码是 EF BB BF。
所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。
项目中乱码解决方案
为了尽量不出现乱码问题,最好确保所有的编码格式保持一致且尽量为UTF-8
-
开发工具本身的编码设置:IDEA, Eclipse等等
-
项目编码格式设置:包括在全局中配置的,web.xml,jsp,html等等
-
服务器编码格式设置:一般都是Linux服务器,locale可以查看服务器的编码格式
-
中间件编码格式:tomcat等等,注意tomcat7及tomcat7以下版本默认的编码格式为 ISO-8859-1 编码(如需修改网上有详细解答),后面的版本则是UTF-8
-
如果是用其他容器部署,比如 docker,也许设置docker容器内部的编码格式,一般跟所在的服务器编码保持一致
-
对于request请求:
设置字符输入流的编码,设置的字符集要和页面保持一致,get请求乱码一般需要做编码转换
request.setCharacterEncoding("UTF-8"); //只对post请求有效
- 对接其他系统时,get请求传参时最好做 URLEncoder 再传过去,防止出现中文乱码
- 文件上传和下载中出现相关乱码问题也基本是以上哪个环节没有做好而导致的
以上就是所有对字符编码的总结了,希望大家不要碰到中文乱码问题了,搞死人了。。。