這是一種比base-64
更加節省空間的編碼方式。github地址
1.概述
在網絡編程中,我們常需要利用一些文本傳輸協議(如POP3
和SMTP
)協議來傳輸二進制數據。
這時候必須要先將它們轉化爲文本格式才能進行傳輸,最常用的編碼方式就是base-64
。它雖然簡單方便,容易實現,但是將會帶來33%
的數據量增長。
2.一個小例子
有些時候我們想把圖片、字體、音頻等二進制文件作爲url
的一部分傳輸給客戶端/服務器,這樣可以減少一次請求和響應。當然,我們必須保證這些文件不能太大。
比如,原來是這樣寫的:
<img src="example.png" />
爲了避免這次額外的資源請求,我們可以利用base-64
編碼從而換成下面的寫法:
<img src="data:image/png;base64,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" />
雖然這種做法可能對於小文件來說確實可以優化整個數據傳輸的效率,但是如果不慎用於大文件將很可能導致性能的下降。原因就是base-64
編碼會導致額外增加33%
的傳輸量。下面講簡要分析其原因。
3.base-64編碼
base-64
是將二進制數據轉換爲文本數據的最常用方法,比如說要把一個字節的二進制數據–01101001
插入到一個文本文件中。最直接的方法就是找兩個字符分別與0
、1
對應,如果我們選擇的是A
和B
,那麼轉換的結果就是下面這樣的(假設這是一個只有1個
像素的圖片文件)。
<img src="data:image/png;sillyEncoding,ABBABAAB" />
雖然實現起來很簡單,但是編碼後的數據會變爲原來的八倍。
對於base-64
,他會把數據按照每6個
位分爲一組(一共有64
種組合方式),每種組合方式(編號爲0
~63
)各對應一個字符,然後進行編碼。這樣轉換前後的數據量之比就爲8:6
。
如果原數據的數據量(以位爲單位計算)不是6
的倍數,添加1個
或2個
額外的字節來湊足24位
,然後分成3組
後再對應編碼。但是這1個
或2個
額外添加的字節不參與編碼,而是最後統一轉換成1個或2個=
號。
那麼,如果使用base-64
來編碼上面的圖片文件,結果將變成:
<img src="data:image/png;base64,aQ==" />
base-64
編碼使用的字符包括26個大寫字母
+26個小寫字母
+10個阿拉伯數字
,最後還有兩個特殊符號+
和/
。
爲了改良base-64
,我們可以考慮使用其他的編碼字符,但是ASCII
的限制太多,很難再找到更加高效並且簡單可行的映射關係,所以我們下面嘗試在UTF-8
中尋找解決方案。
4.UTF-8編碼
因爲大部分的網頁文件(尤其是有漢字的)其實是由UTF-8
編碼而成的,所以我們的想法是完全可行的。
下面我們開始介紹關於UTF-8
的一些核心概念。
UTF-8
是一種對於Unicode
的可變長字符編碼方式,一共支持1,112,064
個符號點。所謂符號點,其實就是可以表示爲一個字符的數字(在Unicode
中,並不是所有的數字都可以轉換表示成一個字符)。對於ASCII
字符的編碼,可以只用一個字節;但是對於某些的符號點,就可能需要使用多達4個
字節來表示這個字符。
Unicode中符號點的範圍 | UTF-8編碼 | 所用的總位數 | 符號點所佔的位數 | 增加率 |
---|---|---|---|---|
0x00 – 0x7F | 0xxxxxxx | 8 | 7 | 8:7 |
0x80 – 0x7FF | 110xxxxx 10xxxxxx | 16 | 11 | 16:11 |
0x800 – 0xFFFF | 1110xxxx 10xxxxxx 10xxxxxx | 24 | 16 | 24:16 |
0x10000 – 0x10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 32 | 21 | 32:21 |
上面的增加率,就是表示該範圍內的符號數所用的位數和實際上真正有效的位數(其他的位只是作爲輔助標識)。
如果使用上表中的單字節編碼方式來將我們的二進制數據轉換成UTF-8
編碼,那麼結果將會是下面這樣子的:
如果使用這種每7位1組
的編碼方式,那麼編碼之後得到的數據量將從base64
的8:6
下降爲8:7
。
但是很遺憾,如果在網頁中使用這種編碼方式,將會引起衝突,不然base64
也不會從ASCII
編碼字符只中選取其中的64個
作爲目標字符,原因還是爲了避免在網頁解析時發生衝突。
比如,我們轉換後的數據一般是以下面這種形式出現在網頁代碼中的:
<img src="data:image/png;ourEncoding,(Encoded data)" />
(如果數據中出現",這個字符就可能使瀏覽器誤以爲是字符串的結束,進而造成數據截斷)。
5.如何避免衝突
爲了保證編碼後得到的字符不會在HTML
頁面中引起衝突,我們需要找出所有的“不安全”的字符。(比如雙引號"
)
除了雙引號"
以外,還有換行字符\n
、回車字符\r
、轉義字符\
和&
、不可顯示的null字符
–0x00
都會引起解析衝突。
這樣,把上面的6個
”不安全“字符剔除後,只剩下122個
字符。
6.base-122編碼
這樣我們的方案基本成型了,將原二進制數據按照每7位
爲一組(如果數據長度不是7
的倍數,則需要填充),然後直接轉換爲UTF-8
中的單字節字符,0xxxxxxx
。
如果轉換後的字符不湊巧就是6個
“不安全”的字符之一,則做進一步的轉換,將其變成雙字節的UTF-8
字符:110xxxxx 10xxxxxx
。因爲只有6個
字符,其實只需要3個
位就行了,這裏我們使用sss
來表示這些實際有效的數據位,110sssxx 10xxxxxx
。
那麼剩下來還有8個
位可以存放數據,這8個位
完全足夠存放下一組的7個位
的數據。這樣我們選後面的7位
來存放下一組的數據即可,110sss1x 10xxxxxx
(sss
用來標識不安全的6個
字符,xxxxxxx
用來存放下一組的數據)。
base-122
編碼的最終效果如下:
終於,我們完成了編碼前後數據比爲7:8
的目標,編碼同樣的數據,所佔用的數據量是base-64
的86%
。
7.如何使用
使用前必須保證你的HTML
文件使用的是UTF-8
編碼格式。
<head><meta charset="utf-8"></head>
用法見github
首頁的介紹。