Base64是最常用的編碼之一,比如開發中用於傳遞參數、現代瀏覽器中的<img />標籤直接通過Base64字串來渲染圖片以及用於郵件中等等。 Base64編碼在RFC2045中定義,它被定義為:Base64內容傳送編碼被設計用來把任意序列的8位位元組描述為一種不易被人直接識別的形式。
我們知道,任何資料在電腦中都是以二進位的方式存儲的。 一個位元組為8位,一個字元在電腦中存儲為一個或多個位元組,比如英文字母、數位以及英文標點符號就是用一個 位元組來存儲的,通常稱為ASCII碼。 而HTTP://www.aliyun.com/zixun/aggregation/23115.html">簡體中文、繁體中文、日文以及韓文等都是用多位元組來存儲的,通常稱為多位元組字元。 因為Base64編碼是對字串的編碼表示進行處理的,不同編碼的字串的Base64的結果是不同的,所以我們需要瞭解基本的字元編碼知識。
字元編碼基礎
電腦最開始只支援ASCII碼,一個字元用一個位元組表示,只用了低7位,最高位為0,因此總共有128個ASCII碼,範圍為0~127。 後來為了支援多種地區的語言,各大組織機構和IT廠商開始發明它們自己的編碼方案,以便彌補ASCII編碼的不足,如GB2312編碼、GBK編碼和Big5編碼等。 但這些編碼都只是針對局部地區或少數語言文字,沒有辦法表達所有的語言文字。 而且這些不同的編碼之間並沒有任何聯繫,它們之間的轉換需要通過查表來實現。
為了提高電腦的資訊處理和交換功能,使得世界各國的文字都能在電腦中處理,從1984年起,ISO組織就開始研究制定一個全新的標準:通用多八位(即多位元組)編碼字元集(Universal Multiple-Octet Coded Character Set),簡稱UCS。 標準的編號為:ISO 10646。 這一標準為世界各種主要語言的字元(包括簡體及繁體的中文字)及附加符號,編制統一的內碼。
統一碼(Unicode)是Universal Code的縮寫,是由另一個叫「Unicode學術學會」(The Unicode Consortium)的機構制定的字元編碼系統。 Unicode與ISO 10646國際編碼標準從內容上來說是同步一致的。 具體可參考:Unicode 。
ANSI
ANSI不代表具體的編碼,它是指本地編碼。 比如在簡體版windows上它表示GB2312編碼,在繁體版windows上它表示Big5編碼,在日文作業系統上它表示JIS編碼。 所以如果您新建了個文字檔並保存為ANSI編碼,那麼您現在應該知道這個檔的編碼為本地編碼。
Unicode
Unicode編碼是和字元表一一映射的。 比如56DE代表漢字'回',這種映射關係是固定不變的。 通俗的說Unicode編碼就是字元表的座標,通過56DE就能找到漢字'回'。 Unicode編碼的實現包括UTF8、UTF16、UTF32等等。
Unicode本身定義的就是每個字元的數值,是字元和自然數的映射關係,而UTF-8或者UTF-16甚至UTF-32則定義了如何在位元組流中斷字,是電腦領域的概念。
通過上圖我們知道,UTF-8編碼為變長的編碼方式,占1~6個位元組,可通過Unicode編碼值的區間來判斷,並且每個組成UTF8字元的位元組都是有規律可循的。 本文只討論UTF8和UTF16這兩種編碼。
UTF16
UTF16編碼使用固定的2個位元組來存儲。 因為是多位元組存儲,所以它的存儲方式分為2種:大端序和小端序。 UTF16編碼是Unicode最直接的實現方式,通常我們在windows上新建文字檔後保存為Unicode編碼,其實就是保存為UTF16編碼。 UTF16編碼在windows上採用小端序的方式存儲,以下我新建了個文字檔並保存為Unicode編碼來測試,檔中只輸入了一個漢字'回',之後我用Editplus打開它,切換到十六進位方式查看,如圖所示:
我們看到有4個位元組,前2個位元組FF FE是檔頭,表示這是一個UTF16編碼的檔,而DE 56則是'回'的UTF16編碼的十六進位。 我們經常使用的JavaScript語言,它內部就是採用UTF16編碼,並且它的存儲方式為大端序,來看一個例子:
<script type="text/javascript">
console.group('Test Unicode: ');
console.log(('回'.charCodeAt(0)).toString(16).toUpperCase());
</script>
很明顯跟剛才Editplus顯示的不一樣,順序是相反的,這是因為位元組序不一樣。 具體可參考:UTF-16 。
UTF8
UTF8是採用變長的編碼方式,為1~6個位元組,但通常我們只把它看作單位元組或三位元組的實現,因為其它情況實在少見。 UTF8編碼通過多個位元組組合的方式來顯示,這是電腦處理UTF8的機制,它是無位元組序之分的,並且每個位元組都非常有規律,詳見上圖,在此不再詳述。
UTF16和UTF8的相互轉換
UTF16轉UTF8
UTF16和UTF8之間的相互轉換可以通過上圖的轉換表來實現,判斷Unicode碼所在的區間就可以得到這個字元是由幾個位元組所組成,之後通過移位來實現。 我們用漢字'回'來舉一個轉換的例子。
我們已經知道漢字'回'的Unicode碼是0x56DE,它介於U+00000800 – U+0000FFFF之間,所以它是用三個位元組來表示的。
所以我們需要將0x56DE這個雙位元組的值變為三位元組的值,注意上圖中的x部分,就是對應0x56DE的各位位元組,如果您數一下x的個數,會發現剛好是16位。
轉換思路
從0x56DE中取出4位放在低位,並和二進位的1110結合,這就是第一個位元組。 從0x56DE中剩下的位元組中取出6位放在低位,並和二進位的10結合,這就是第二個位元組。 第三個位元組依照類似的方式實現。
代碼實現
為了讓大家更好的理解,以下代碼只是實現漢字'回'的轉換,代碼如下:
<script type="text/javascript">
/**
* 轉換對照表
* U+00000000 – U+0000007F 0xxxxxxx
* U+00000080 – U+000007FF 110xxxxx 10xxxxxx
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
* U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
* U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
* U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
/*
* '回'的Unicode編碼為:0x56DE,它介於U+00000800 – U+0000FFFF之間,所以它佔用三個位元組。
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
*/
var ucode = 0x56DE;
// 1110xxxx
var byte1 = 0xE0 | ((ucode >> 12) & 0x0F);
// 10xxxxxx
var byte2 = 0x80 | ((ucode >> 6) & 0x3F);
// 10xxxxxx
var byte3 = 0x80 | (ucode & 0x3F);
var utf8 = String.fromCharCode(byte1)
+ String.fromCharCode(byte2)
+ String.fromCharCode(byte3);
console.group('Test UTF16ToUTF8: ');
console.log(utf8);
console.groupEnd();
</script>
輸出的結果看起來像亂碼,這是因為JavaScript不知道如何顯示UTF8的字元。 您或許會說輸出不正常轉換還有什麼用,但您應該知道轉換的目的還經常用於傳輸或API的需要。
UTF8轉UTF16
這是UTF16轉換到UTF8的逆轉換,同樣需要對照轉換表來實現。 還是接上一個例子,我們已經得到了漢字'回'的UTF8編碼,這是三個位元組的,我們只需要按照轉換表來轉成雙位元組的,如圖所示,我們需要保留下所有的x。
代碼如下:
<script type="text/javascript">
/**
* 轉換對照表
* U+00000000 – U+0000007F 0xxxxxxx
* U+00000080 – U+000007FF 110xxxxx 10xxxxxx
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
* U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
* U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
* U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
/*
* '回'的Unicode編碼為:0x56DE,它介於U+00000800 – U+0000FFFF之間,所以它佔用三個位元組。
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
*/
var ucode = 0x56DE;
// 1110xxxx
var byte1 = 0xE0 | ((ucode >> 12) & 0x0F);
// 10xxxxxx
var byte2 = 0x80 | ((ucode >> 6) & 0x3F);
// 10xxxxxx
var byte3 = 0x80 | (ucode & 0x3F);
var utf8 = String.fromCharCode(byte1)
+ String.fromCharCode(byte2)
+ String.fromCharCode(byte3);
console.group('Test UTF16ToUTF8: ');
console.log(utf8);
console.groupEnd();
/** ------------------------------------------------------------------------------------*/
// 由三個位元組組成,所以分別取出
var c1 = utf8.charCodeAt(0);
var c2 = utf8.charCodeAt(1);
var c3 = utf8.charCodeAt(2);
/*
* 需要通過判斷特定位的方式來轉換,但這裡是已知是三個位元組,所以忽略判斷,而是直接拿到所有的x,組成16位。
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
*/
// 丟棄第一個位元組的高四位並和第二個位元組的高四位組成一個位元組
var b1 = (c1 << 4) | ((c2 >> 2) & 0x0F);
// 同理第二個位元組和第三個位元組組合
var b2 = ((c2 & 0x03) << 6) | (c3 & 0x3F);
// 將b1和b2組成16位
var ucode = ((b1 & 0x00FF) << 8) | b2;
console.group('Test UTF8ToUTF16: ');
console.log(ucode.toString(16).toUpperCase(), String.fromCharCode(ucode));
console.groupEnd();
</script>
知道了轉換規則,就很容易實現了。
Base64編碼
Base64編碼要求把3個8位位元組(3*8=24)轉化為4個6位的位元組(4*6=24),之後在6位的前面補兩個0,形成8位一個位元組的形式。 由於2的6次方為64,所以每6個位為一個單元,對應某個可列印字元。 當原資料不是3的整數倍時,如果最後剩下兩個輸入資料,在編碼結果後加1個「=;如果最後剩下一個輸入資料,編碼結果後加2個「=;如果沒有剩下任何資料,就什麼都不要加,這樣才可以保證資料還原的正確性。
轉碼對照表
每6個單元高位補2個零形成的位元組位於0~63之間,通過在轉碼表中查找對應的可列印字元。 「=」用於填充。 如下圖所示為轉碼表。