字符编码二三事

1、编码历史

  这个着重第一个说,是由于要想了解一个知识就应该去了解他的历史,每一个阶段发生了什么问题,以及如何解决,和出现的目的。php

     编码的发展大概分为三个阶段,出生(ASCII),编码本地化(如GBK,BIG5),国际化(UNICODE)html

    1. ASCII码

      ASCII是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其余西欧语言。山姆大叔于50年代后期(967年定案)搞出来的西文字符编码标准,使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。其中使用7 位二进制数来表示全部的大写和小写字母,数字0 到九、标点符号, 以及在美式英语中使用的特殊控制字符。记得哦,重点是使用了7位二进制来表示,也就是说用了128个字符,只能支持英语java

    2.ANSI(本地化)

      山姆大叔搞出来的编码字符集只能支持英语,但是世界上还有其余各类语言,好比汉语,法语,日韩等,更要命的是,ASCII是一个字节来标识的,最多能表示256个字符,而其余语言远不止这些,怎么办,以后,各国根据ASCII来本地化本身的字符集,好比咱们常见的,GBK(简体中文),BIG5(繁体中文),iso8895系列(包含1-16)等,此时很好的解决本地化问题,让计算机可以显示本地文字。但这个问题是各自为政,玩本身的。程序员

    3.国际化

    本地化以后,世界上存在了各类编码,此时延伸出来了另一个问题,好比游戏,日本作的游戏本国玩没问题,一到了中国就变成一堆乱码了,这还好说,大不了写个程序作转码,可是邮件,各国之间发送和接受都是一堆乱码,这些只能本国使用没法世界流通了,咱们现在的互联网就没法联通世界了。数据库

     此时国际标准组织建议你们来搞一套万国码,不管到哪里都能使用的编码字符集,因而Unicode就诞生了,国际组织制定了 UNICODE 字符集,为各类语言中的每个字符设定了统一而且惟一的数字编号,以知足跨语言、跨平台进行文本转换、处理的要求。具体的unicode介绍本身wiki脑补下,不作具体介绍了,以后的互联网,更是让UTF-8一会儿就火起来,为什么呐,他既省空间又灵活多变,让程序员门爱不释手。网络

2、编码基本概念定义

  上面介绍的编码历史,里面所说的编码主要是字符集,没有着重强调字符编码,切记这两个是有着重区别的,如今网上一堆介绍编码的连这个基本的概念都搞混了,让人误解颇深。编辑器

    1.字库表

       字库表决定了整个字符集可以展示表示的全部字符的范围,你能够这么理解,字库就是那些存在数据库的二进制数据,他只为计算机显示表现用,具体要用那个字,是字符集说了算。字体

    2.字符集

     字符集 虽然叫字符集,但并不存真是的字体数据,是一个抽象的概念,就是全部字符的集合,他相似是每一个字符的位置索引,方便调用方快速定位这个数据。编码

常见字符集有:Unicode字符集、ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集等url

    Unicode 是为了解决传统的字符编码方案的局限而产生的, 也称为万国码,是一个很大的字符集合,如今的规模能够容纳100多万个符号,区间0x0000 至 0x10FFFF,目前字节长度为2或者4个字节

    3.字符编码

    字符编码(encoding)和字符集不一样,字符编码是将编码字符集和实际存储数值之间的转换关系,同一个字符集能够有多种编码方式,好比如Unicode可依不一样须要以UTF-八、UTF-1六、UTF-32等方式编码,也就是说UTF-八、UTF-1六、UTF-32是字符编码,他们归属于Unicode字符集。为什么要对字符集再作一次编码,目的是节省空间。好比unicode,一个字符占用2或者4个字节,但是ascii一个字符一个字节就够了,不必也占用2个字节,用UTF-8的话,能够节省了一半以上的空间,这对于传送速度或者磁盘空间方面是比较节省方案了。

  咱们经常使用的UTF-8是字符编码,归属于Unicode字符集,是Unicode的一种编码方式。

3、经常使用编码解析

    1.GBK

     GBK即“国标”,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,关键是每一个字符都是双字节,包括英文字符。

    2.UTF-8

   UTF-8是当今接受度最广的字符集编码,是一种针对Unicode的可变长度字符编码,主要特色是一种可变长度的编码方式,不在是unicode那种刻板的一个字符占用固定空间,目前字节是1-6个(现在最多用4个)。好比文件里有ascii字符的话,一个字节空间,有中文的话就3个空间。

    UTF-8编码最小编码单位为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分。对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一概设为10。

 好比,从Unicode到UTF-8的编码方式以下:

Unicode编码(十六进制) 

UTF-8 字节流(二进制)

00000000 - 0000007F

0xxxxxxx

00000080 - 000007FF

110xxxxx 10xxxxxx

00000800 - 0000FFFF

1110xxxx 10xxxxxx 10xxxxxx

00010000 - 001FFFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

 

经过上面的编码中咱们能够看到:

  • 3个字节的UTF-8二进制编码第一个字节是以111开头的,第二三字节的前两位为10
  • 2个字节的UTF-8二进制编码第一个字节是以11开头的,第二个字节前两位为10
  • 1个字节的UTF-8二进制编码第一个字节是以0开头的

也就是说若是一个字节的第一位是0,则这个字节单独就是一个字符,所以对于英语字母,UTF-8编码和ASCII码是相同的;若是第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

4、如何解决乱码

 乱码的造成有常见有两种缘由,第一种是是编码和解码时用了不一样或者不兼容的字符集而形成的;第二种是字节序的存储方式不一样而造成  ,好比PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据,两种机器文件交互就会出现乱码。咱们分开来说,通常来讲第一种出现的更多些。

  1.编解码字符集不一样

   1)缘由

  •    字符的传输或者储存实际上是存储字体在对应字符集里的地址,不论是解码仍是编码都是对经过地址找字体,而后显示,如果编码和解码时用了不一样或者不兼容的字符集就会出现乱码。好比发送方采起的编码是GBK,而接收方采用的解码是UTF-8,此时会出现的问题是,接受方拿着表示GBK的字符地址,去Unicode的字符集找数据。
  • 咱们来个例子,“汉字”在UTF-8的内码地址(16进制)以下:
  • 字符

    UTF-8编码后16进制

    E6B189

    E5AD97
  • 解码采用gbk解码,E6B189E5AD97对应的gbk字符库数据是:
  • GBK(16进制数值)

    字符

    E6B1
    89E5
    AD97

  2)解决乱码

            解决乱码主要是要从乱码字符中反解出原来的正确文字,咱们分三步来来反推,解码,识别,编码。

  1. 解码
    当前文件或者程序采用什么编码方式,咱们就须要以相同的形式进行解码,好比刚才的“姹夊瓧”是采用gbk编码,咱们用gbk解码,找到本来的内码地址(二进制),我以java为例:
    byte[] encodingBytes = "姹夊瓧".getBytes("gbk");

     

  2. 识别
    public static final String byte2hex(byte b[]) {
       if (b == null) {
          throw new IllegalArgumentException(
                "Argument b ( byte array ) is null! ");
       }
       String hs = "";
       String stmp = "";
       for (int n = 0; n < b.length; n++) {
          stmp = Integer.toHexString(b[n] & 0xff);
          if (stmp.length() == 1) {
             hs = hs + "0" + stmp;
          } else {
             hs = hs + stmp;
          }
       }
       return hs.toUpperCase();
    }

    咱们把数据打印出来看下对应的16进制是什么,E6B189E5AD97,E开头的,经过以前的了解咱们大概知道发送方是采用UTF-8编码,咱们尝试下。

  3. 编码
    String codingData = new String(encodingBytes, Charset.forName("utf8"));
    System.out.println(codingData);

    知道了编码方式,咱们采用utf8进行编码,看下原始数据,发现是“汉字”。

        以上的是通常反推乱码的方式和原理,其实自己很简单,程序编解码(2行代码左右),或者拿着乱码数据用编辑器uedit来切换编码格式,都会很快反推出来。

  2.字节序不一样

  •    字节序须要单独出来讲的问题,主要是乱码也和这个有关系,跨语言通讯的时候会出现。目前的字节序主要有,Little endian和Big endian之分,也就是常说的大头和小头之分。
  •     big endian(大头方式)是指低地址存放最高有效字节(MSB),而little endian(小头方式)则是低地址存放最低有效字节(LSB)。 
  •      具体是大头在前仍是小头在前,这个和主机的cpu有关系PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。好比“汉”unicode编码,big ending是 6C49 ,就是:6C在前(大头),49在后, ,如果little endian的话,就是:49在前(大头),6C在后。
  •     总之记住一条规则就够了,咱们常看到的二进制排码顺序(左高右低),都是big endian,也称为网络字节序,网络字节顺序采用big endian排序方式。

 

引用:

http://blog.163.com/wangxuefan1220@126/blog/static/8821147201231331838952/

http://www.joelonsoftware.com/articles/Unicode.html

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

http://blog.jobbole.com/84903/

http://www.mytju.com/classCode/tools/encode_gb2312.asp

http://blog.csdn.net/xufenghfut/article/details/11585311

相关文章
相关标签/搜索