浅谈编码集
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈编码集相关的知识,希望对你有一定的参考价值。
一、名词解释
在聊编码集之前,我们先来了解一些名词解释:
字符集:所谓字符编码就是一个系统支持的所有抽象字符的集合,也就是说我们平常使用的文字,标点符号,图形符号等都是字符集。
我们知道,计算机无法识别我们平时说的文字,只能识别二进制的数字系统,那么就需要一套规则,将我们所说的字符转换为数字系统,那么这种操作,就是字符编码。官方解释如下:
字符编码:将符号转换为计算机可以接受的数字系统的规则。
理解了以上两个概念,接下来的两个概念就很容易理解了:
编码:按照某种字符编码将字符存储在计算机中。
解码:将存储在计算机中的二进制数解析出来。
二、编码集的发展史
ASCII编码
ASCII使用8个bit表示一个字节,共有128个字符,编码范围是0-127。
众所周知,计算机最先使用是在美国,ASCII编码作为美国“元老级编码”,被称作“美国信息交换标准代码”。
ASCII字符集主要包括两部分,分别是:控制字符 和 可显示字符
控制字符:主要包括回车键、退格键、换行键等
可显示字符:主要包括英文大小写、阿拉伯数字和西文符号等
ISO-8859-1
在ASCII推出后,计算机也在迅猛发展,当西欧等国家也开始使用计算机时,发现,ASCII虽然在显示英文方面做的很好,但是西欧国家的一些语言是无法显示的,所以,他们就对ASCII进行拓展。他们将新的符号填入128-255,将编码范围从0-127拓展成为0-255,共256个字符。相较于ASCII,ISO-8859-1加入了西欧语言,同时还加入了横线、竖线、交叉等符号。
GB系列的诞生
随着计算机的普及,我们国家也开始使用计算机,但是如何显示中文就成了一个大难题。
使用ASCII对中文进行编码和解码(以JAVA代码为例):
String chineseStr = "哈哈"; //编码 byte[] ascii = chineseStr.getBytes("ASCII"); //解码 String asciiStr = new String(ascii,"ASCII"); System.out.println("使用ASCII编码显示中文"+asciiStr);
运行结果不出所料,中文变成了“??”,可见,ASCII无法满足我们对中文的需要,可是已经没有可用的字节给我们用了,这可难不倒我们中国的劳动人民。那我们是怎么做的呢?
我们把127号之后的所有符号(即ASCIIM拓展码)取消掉,并且规定:一个小于127的字符,意义与原来相同,两个大于127的字符连在一起表示一个汉字。
就这样,我们组合出7000多个常用简体汉字,还包括数字符号、罗马希腊字母、日文假名们。这就是我们常说的GB2312编码
但是,中国的汉字实在是太多了,还有繁体字也没有编进GB2312中,同时,在GB2312推出后又增加了许多简体字,这些汉字还是无法显示。
还好,GB2312并没有把所有的码位都用完。但是当我们把剩下的码位填满之后,发现还是有很多汉字无法编入,我们天朝的专家又说了,不再要求第二个字节也是127号以后的字符,只要第一个字节大于127就表示一个汉字的开始。
这次的编码,增加了进20000个汉字(包括繁体字)和符号,我们把这中编码成为GBK编码集。GBK编码包括了GB2312的所有内容。
那么问题来了,之前使用ASCII编码的软件可以在GB系列环境下继续运行吗?
使用ASCII编码,使用GBK解码:
String englishStr = "hello world"; //编码 byte[] ascii = englishStr.getBytes("ASCII"); //解码 String gbkStr = new String(ascii,"GBK"); System.out.println("使用ASCII编码,使用GBK解码:"+gbkStr);
运行结果:没有发生乱码
使用ASCII编码,使用GBK解码:hello world
可见,GB系列解决了显示中文的问题,同时还保证了ASCII遗留软件还可以继续运行。
unicode编码
在中国推出自己的GB系列编码的时候,其他国家也都推出了属于自己语言的编码集,各个国家的软件无法做到互通,因为当编码集不同时,乱码问题就会出现。
终于有一个叫ISO的国际组织实在是看不下去了,他们推出了一个新的编码:unicode编码。
unicode编码是一个很大的编码,它废除了所有地区性的编码方案,重新规定了编码方式,包括了地球上所有文化、所有字母和符号。
它要求:
ASCII里的字符保持原编码不变,只是将其长度由原来的8位拓展成了16位;
其他文化语言的字符则全部统一重新编码,同样也是16位。
我们来看一下unicode显示各国语言的效果如何:
String testStr = "abc哈哈??あはは"; //编码 byte[] unicode = testStr.getBytes("unicode"); //解码 String unicodeStr = new String(unicode,"unicode"); System.out.println("使用UNICODE编码和解码:"+unicodeStr);
运行结果:未发生乱码
使用UNICODE编码和解码:abc哈哈??あはは
我们刚才说到,unicode在处理英文时,将其长度拓展为16位,那么也就是说,在处理同等长度的纯英文字符串时,Unicode要使用两倍的空间来存储,为了能更直观地表达意思,使用代码来向大家说明:
String englishStr = "helloworld" ; //使用ASCII编码 byte[] ascii = englishStr.getBytes("ASCII"); //使用UNICODE编码 byte[] unicode = englishStr.getBytes("unicode"); System.out.println("使用ASCII编码后字节数组长度:"+ascii.length); System.out.println("使用UNICODE编码后字节数组长度:"+unicode.length);
运行结果:
使用ASCII编码后字节数组长度:10 使用UNICODE编码后字节数组长度:22
果然UNICODE的字节数组长度确实是ASCII的两倍。
在这边稍作解释:为什么UNICODE的字节数组的长度是22而不是20.
这是因为,UNICODE属于多字节编码,在存储多字节时,cpu有两种存储方式,分别是大端模式和小端模式。例如,“汉”字的UNICODE编码为6c49,在存储6c49时,CPU要判断是要将6c存在前面还是把49存在前面,如果是大端模式,那么将先存6c,再存49,如果是小端模式,则先存49再存6c。unicode多出的两位其实就是指定使用哪种存储模式,但是在编码过程中是毫无意义的,也就是说Unicode确实要比ASCII花费两倍的空间来存储纯英文文本。
现在我们来总结一下unicode的优缺点:
首先unicode在避免乱码上面功不可没,java底层的编码集就是使用的UNICODE,但同时,它在存储纯英文文本时要比ASCII花费两倍的存储空间,而且,它不与任何一种编码集兼容。
UTF-8
在很长一段时间内,unicode一直没有得到广泛应用,直到互联网的出现,为了解决Unicode的传输问题,出现了一系列的新的编码:UTF系列
其中utf-8编码是在互联网上使用最广的一种UNICODE的实现方式。
utf-8最大的特点就是:它是一种可变长度的编码
utf-8对于不同范围的unicode编码,都有不同的长度来表示,分别使用1-4个字节表示一个符号
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的
对于N字节的符号,第一个字节前N位都设为1,第N+1位设为0,后面的两个字节一律设为10,剩下没有提及的二进制全部为这个符号的Unicode码。
这样说是有点抽象,我们来举一个栗子详细说明:
以汉字“严”为例,严的unicode码为4E25,转成二进制就是100111000100101,4E25处于00000800-0000FFFF中,所以严的utf-8的编码应为3个字节。接下来,将严的unicode二进制表示从最后一位开始依次从后向前填入格式中的X,多出的位补0,最终得到严的utf-8的表示:11100100 10111000 10100101,转为十六进制就是E4B8A5。
最后再来总结一下utf-8的优缺点:
优点:对于单字节编码,utf-8与ASCII是一样的,可以说utf-8就是ASCII的超集,所以大量只支持AASCII的遗留软件可以在UTF-8环境下继续工作;
对于纯英文文本,相较于UNICODE,utf-8节约了一半的存储空间
缺点:比如刚才的栗子,“严”的unicode只需要两个字节,但是utf-8却需要3个字节,可见,在存储中文时,要花费1.5-2倍的空间。
三、两种乱码情况
说了这么多,我们只是为了要避免一个问题,那就是乱码。造成乱码的原因有许多种,在这里只先向大家介绍两种:
第一种:中文成了看不懂的字符
String chineseStr = "哈哈"; //编码 byte[] gbks = chineseStr.getBytes("GBK"); //解码 String gbksStr = new String(gbks,"ISO-8859-1"); System.out.println("使用GBK进行编码,使用ISO-8859-1进行解码"+gbksStr );
运行结果:
使用GBK进行编码,使用ISO-8859-1进行解码1t1t
运行结果显示,中文变成了我们看不懂的字符,那么,这种情况往往是由于,编码和解码使用了两种不兼容的编码集,导致中文变成了其他语言符号。
底层运行过程如下:
字符串转为字符数组,之后字符数组通过GBK转为字节数组,由于ISO和GBK的编码方式完全不同,所以将字节数组转为字符数组时“曲解”了语意。
第二种:中文变成了“?”
String chineseStr = "哈哈"; //编码 byte[] iso = chineseStr.getBytes("ISO-8859-1"); //解码 String isoStr = new String(iso,"ISO-8859-1"); System.out.println("使用ISO-8859-1进行编码,使用ISO-8859-1进行解码"+isoStr);
运行结果:
使用ISO-8859-1进行编码,使用ISO-8859-1进行解码??
运行结果显示,中文统一变成了“?”。那这又是为什么呢?编码和解码的过程中使用的相同的编码集怎么还会乱码呢?那是因为中文需要多字节编码,而ASCII是单字节编码,由于ASCII无法识别多字节,所以进行了过滤,将所有的中文都过滤成了“?”,底层执行原理如下:
以上是关于浅谈编码集的主要内容,如果未能解决你的问题,请参考以下文章