1. 程式人生 > 其它 >Okio原理分析之字元編碼

Okio原理分析之字元編碼

技術標籤:開源框架分析okio編碼utf-8utf-16GBK

OKio的原理分析,準備分3個部分來分析:

  • 字元編碼 先了解一些背景知識,Okio裡面基本上是基於UTF-8來編碼實現的
  • Okio簡介 熟悉OKio裡面引入的一些概念,如Source、Sink、Timeout、Buffer、Segment、SegmentPool、ByteString等
  • Okio裡面Segment資料移動管理 OKio高效的原因,在資料移動方面的一些巧妙的設計,來節約記憶體和節省CPU

1.字元編碼

字元編碼(Character encoding)、字集碼是把字符集中的字元編碼為指定集合中某一物件(例如:位元模式、自然數序列、8位組或者電脈衝),以便文字在計算機中儲存和通過通訊網路的傳遞。常見的例子包括將拉丁字母表編碼成摩斯電碼和ASCII。其中,ASCII將字母、數字和其它符號編號,並用7位元的二進位制來表示這個整數。通常會額外使用一個擴充的位元,以便於以1個位元組的方式儲存。

在計算機系統裡面,編碼的原因可以總結為:

  • 計算機中儲存資訊的最小單元是一個位元組即 8 個 bit,所以能表示的字元範圍是 0~255 個
  • 人類要表示的符號太多,無法用一個位元組來完全表示
  • 要解決這個矛盾必須需要一個新的資料結構 char,從 char 到 byte 必須編碼
UCS和Unicode

通用字符集(英語:Universal Character Set, UCS)是由ISO制定的ISO 10646(或稱ISO/IEC 10646)標準所定義的標準字符集。

通用字符集包括了其他所有字符集。它保證了與其他字符集的雙向相容,即,如果你將任何文字字串翻譯到UCS格式,然後再翻譯回原編碼,你不會丟失任何資訊。

Unicode,中文又稱萬國碼、國際碼、統一碼、單一碼,是電腦科學領域裡的一項業界標準

Unicode規範定義了,每個檔案的最前面分別加入一個表示編碼順序的字元,用FEFF來表示,正好是2個位元組。如果文字檔案的頭2個位元組是FEFF,表示用的大頭方式(第一個位元組在前),用FFFE的表示用的小頭方式(第二個位元組在前)

Unicode是一個符號集合(字符集),定義了符號的二進位制程式碼,但是沒有規定如何儲存二進位制程式碼

Unicode和UCS是來自2個不同的組織,Unicode對集合添加了一些新的規則和規範

歷史上存在兩個獨立的嘗試創立單一字符集的組織

  • 國際標準化組織(ISO)於1984年建立的ISO/IEC
  • 由Xerox、Apple等軟體製造商於1988年組成的統一碼聯盟。前者開發的ISO/IEC 10646專案,後者開發的Unicode專案。因此最初制定了不同的標準。

從Unicode 2.0開始,Unicode採用了與ISO 10646-1相同的字型檔和字碼;ISO也承諾,ISO 10646將不會替超出U+10FFFF的UCS-4編碼賦值,以使得兩者保持一致。兩個專案仍都獨立存在,並獨立地公佈各自的標準。但統一碼聯盟和ISO/IEC JTC1/SC2都同意保持兩者標準的碼錶相容,並緊密地共同調整任何未來的擴充套件。在釋出的時候,Unicode一般都會採用有關字碼最常見的字型,但ISO 10646一般都儘可能採用Century字型

UTF

Unicode的實現方式不同於編碼方式。一個字元的Unicode編碼是確定的。但是在實際傳輸過程中,由於不同系統平臺的設計不一定一致,以及出於節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱為Unicode轉換格式(Unicode Transformation Format,簡稱為UTF)。

UTF是"Unicode/UCS Transformation Format"的首字母縮寫,即把Unicode字元轉換為某種格式之意。UTF-16正式定義於ISO/IEC 10646-1的附錄C,而RFC2781也定義了相似的做法。

編碼格式
  • ASCII :美國製定了一套字元編碼,對英語字元與二進位制位之間的關係,做了統一規定。規定了128個字元的編碼,用一個byte來表示,只佔用7個bit,最高為0
  • ISO-8859-1:ISO-8859-1 仍然是單位元組編碼,它總共能表示 256 個字元
  • GB2312 雙位元組編碼,總的編碼範圍是 A1-F7,其中從 A1-A9 是符號區,總共包含 682 個符號,從 B0-F7 是漢字區,包含 6763 個漢字
  • GBK 它的出現是為了擴充套件 GB2312,加入更多的漢字,它的編碼範圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 相容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,並且不會有亂碼。
  • UTF-16 是Unicode字元編碼五層次模型的第三層:字元編碼表(Character Encoding Form,也稱為"storage format")的一種實現方式。即把Unicode字符集的抽象碼位對映為16位長的整數(即碼元)的序列,用於資料儲存或傳遞。Unicode字元的碼位,需要1個或者2個16位長的碼元來表示,因此這是一個變長表示。
  • UTF-8:是Unicode的實現方式之一,使用變長的編碼形式,可以用1-6個byte來表示一個符號(In UTF-8, characters are encoded using sequences of 1 to 6 octets.)

UTF-8 有以下編碼規則:

  • 如果一個位元組,最高位(第 8 位)為 0,表示這是一個 ASCII 字元(00 – 7F)。可見,所有 ASCII 編碼已經是 UTF-8 了。
  • 如果一個位元組,以 11 開頭,連續的 1 的個數暗示這個字元的位元組數,例如:110xxxxx 代表它是雙位元組 UTF-8 字元的首位元組。
  • 如果一個位元組,以 10 開始,表示它不是首位元組,需要向前查詢才能得到當前字元的首位元組
   UCS-4 range (hex.)     UTF-8 octet sequence (binary)
 
   0000 0000-0000 007F   0xxxxxxx
 
   0000 0080-0000 07FF   110xxxxx 10xxxxxx
 
   0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx

   0001 0000-001F FFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 
   0020 0000-03FF FFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
 
   0400 0000-7FFF FFFF   1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

詳細的編碼說明可以參考:https://tools.ietf.org/html/rfc2279

2.Java裡面的編碼說明

Java字元和位元組
  • 二進位制位bit:計算機,所有的資訊都是bit的形式存在,每個bit有0和1兩種狀態
  • 位元組byte:8個二進位制位是一個byte,1 byte = 8 bit
  • 字元Character:1 char = 2 byte = 16 bit(Java對字元預設是UTF-16編碼)

此節內容,擷取自深入分析 Java 中的中文編碼問題

以字串”I am 君山”的 char 陣列為 49 20 61 6d 20 541b 5c71,為例,下面把它按照不同的編碼格式轉化成相應的位元組。

    public static void encode() {
        String name = "I am 君山";
        System.out.println( " default charset = " + Charset.defaultCharset()+" : " +bytesToHex(name.getBytes()));
        try {
            byte[] iso8859 = name.getBytes("ISO-8859-1");
            System.out.println("ISO-8859-1 = " + bytesToHex(iso8859));
            byte[] gb2312 = name.getBytes("GB2312");
            System.out.println("GB2312 = " + bytesToHex(gb2312));
            byte[] gbk = name.getBytes("GBK");
            System.out.println("GBK = " + bytesToHex(gbk));
            byte[] utf16 = name.getBytes("UTF-16");
            System.out.println("UTF-16 = " + bytesToHex(utf16));
            byte[] utf8 = name.getBytes("UTF-8");
            System.out.println("UTF-8 = " + bytesToHex(utf8));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public static String bytesToHex(byte[] bytes) {
        char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

執行結果是:

 default charset = UTF-8 : 4920616D20E5909BE5B1B1
ISO-8859-1 = 4920616D203F3F
GB2312 = 4920616D20BEFDC9BD
GBK = 4920616D20BEFDC9BD
UTF-16 = FEFF004900200061006D0020541B5C71
UTF-8 = 4920616D20E5909BE5B1B1

按照 ISO-8859-1 編碼
在這裡插入圖片描述

按照 GB2312 編碼
在這裡插入圖片描述

按照 GBK 編碼
在這裡插入圖片描述

按照 UTF-16 編碼
在這裡插入圖片描述

按照 UTF-8 編碼
在這裡插入圖片描述