字符編碼二三事

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

相關文章
相關標籤/搜索