思考一下,爲何有字符編碼這種東西?html
固然是爲了讓計算機「聽話」唄。咱們知道,計算機的世界只有01這兩個字符,而咱們現實世界有成千上萬的字符。如何用01的組合去和現實中的字符一一對應呢?這就是須要制定相應的編碼規則來實現了。明白了這點,咱們正式開始編碼的講解。前端
咱們知道,在計算機內部,全部的信息最終都表示爲一個二進制的字符串。每個二進制位(bit)有0和1兩種狀態,所以八個二進制位就能夠組合出256種狀態(-128~127),這被稱爲一個字節(byte)。也就是說,一個字節一共能夠用來表示256種不一樣的狀態,每個狀態對應一個符號,就是256個符號,從0000000到11111111。segmentfault
上個世紀60年代,美國製定了一套字符編碼,對英語字符與二進制位之間的關係,作了統一規定。這被稱爲ASCII碼,一直沿用至今。數組
ASCII碼一共規定了128個字符的編碼,好比空格「SPACE」是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面7位,最前面的1位統一規定爲0。瀏覽器
ASCII碼用了1個字節,1個字節能夠表示256種狀態,但ASCII碼只用了128種,也就是一個字節的後七位,最前面的1位都是0。安全
英語用128個符號編碼就夠了,可是用來表示其餘語言,128個符號是不夠的。好比,在法語中,字母上方有注音符號,它就沒法用ASCII碼錶示。因而,一些歐洲國家就決定,利用字節中閒置的最高位編入新的符號。好比,法語中的é的編碼爲130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,能夠表示最多256個符號。服務器
可是,這裏又出現了新的問題。不一樣的國家有不一樣的字母,所以,哪怕它們都使用256個符號的編碼方式,表明的字母卻不同。好比,130在法語編碼中表明瞭é,在希伯來語編碼中卻表明了字母Gimel (ג),在俄語編碼中又會表明另外一個符號。可是無論怎樣,全部這些編碼方式中,0—127表示的符號是同樣的,不同的只是128—255的這一段。網絡
至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節只能表示256種符號,確定是不夠的,就必須使用多個字節表達一個符號。好比,簡體中文常見的編碼方式是GB2312,使用兩個字節表示一個漢字,因此理論上最多能夠表示256x256=65536個符號。
中文編碼的問題須要專文討論,這篇筆記不涉及。這裏只指出,雖然都是用多個字節表示一個符號,可是GB類的漢字編碼與後文的Unicode和UTF-8是毫無關係的。app
正如上一節所說,世界上存在着多種編碼方式,同一個二進制數字能夠被解釋成不一樣的符號。所以,要想打開一個文本文件,就必須知道它的編碼方式,不然用錯誤的編碼方式解讀,就會出現亂碼。爲何電子郵件經常出現亂碼?就是由於發信人和收信人使用的編碼方式不同。ide
能夠想象,若是有一種編碼,將世界上全部的符號都歸入其中。每個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是Unicode,就像它的名字都表示的,這是一種全部符號的編碼。
Unicode(統一碼、萬國碼、單一碼)是計算機科學領域裏的一項業界標準,包括字符集、編碼方案等。Unicode 是爲了解決傳統的字符編碼方案的侷限而產生的,它爲每種語言中的每一個字符設定了統一而且惟一的二進制編碼,以知足跨語言、跨平臺進行文本轉換、處理的要求。
Unicode是國際組織制定的能夠容納世界上全部文字和符號的字符編碼方案。目前的Unicode字符分爲17組編排,0x0000 至 0x10FFFF,每組稱爲平面(Plane),而每平面擁有65536個碼位,共1114112個。然而目前只用了少數平面。UTF-八、UTF-1六、UTF-32都是將數字轉換到程序數據的編碼方案。
Unicode固然是一個很大的集合,如今的規模能夠容納100多萬個符號。每一個符號的編碼都不同,好比,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字「嚴」。具體的符號對應表,能夠查詢unicode.org,或者專門的漢字對應表。
須要注意的是,Unicode只是一個符號集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。好比,漢字「嚴」的unicode是十六進制數4E25,轉換成二進制數足足有15位(100111000100101),也就是說這個符號的表示至少須要2個字節。表示其餘更大的符號,可能須要3個字節或者4個字節,甚至更多。
這裏就有兩個嚴重的問題:1. 如何才能區別unicode和ascii?計算機怎麼知道三個字節表示一個符號,而不是分別表示三個符號呢?2. 咱們已經知道,英文字母只用一個字節表示就夠了,若是unicode統一規定,每一個符號用三個或四個字節表示,那麼每一個英文字母前都必然有二到三個字節是0,這對於存儲來講是極大的浪費,文本文件的大小會所以大出二三倍,這是沒法接受的。
它們形成的結果是:
互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8就是在互聯網上使用最廣的一種unicode的實現方式。其餘實現方式還包括UTF-16和UTF-32,不過在互聯網上基本不用。重複一遍,這裏的關係是,UTF-8是Unicode的實現方式之一。
UTF-8最大的一個特色,就是它是一種變長的編碼方式。它可使用1~4個字節表示一個符號,根據不一樣的符號而變化字節長度。
UTF-8的編碼規則很簡單,只有二條:
下表總結了編碼規則,字母x表示可用編碼的位。
Unicode符號範圍 | UTF-8編碼方式
(十六進制) | (二進制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
下面,仍是以漢字「嚴」爲例,演示如何實現UTF-8編碼。
已知「嚴」的unicode是4E25(100111000100101),根據上表,能夠發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),所以「嚴」的UTF-8編碼須要三個字節,即格式是「1110xxxx 10xxxxxx 10xxxxxx」。而後,從「嚴」的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就獲得了,「嚴」的UTF-8編碼是「11100100 10111000 10100101」,轉換成十六進制就是E4B8A5。
那unicode和UTF-8有何區別?
Unicode 是「字符集」
UTF-8 是「編碼規則」
字符集:爲每個「字符」分配一個惟一的 ID(學名爲碼位 / 碼點 / Code Point)
編碼規則:將「碼點」轉換爲字節序列的規則
參考:字符編碼筆記:ASCII、Unicode、UTF-8 和 Base64
上面講的是字符編碼,是指一個字符對應的一個二進制數。而BCD碼是計算機在對十進制數作運算或存儲時採用的二進制格式。
Binary-Coded Decimal,簡稱BCD,稱BCD碼或二-十進制代碼,亦稱二進碼十進數。是一種二進制的數字編碼形式,用二進制編碼的十進制代碼。這種編碼形式利用了四個位元來儲存一個十進制的數碼,使二進制和十進制之間的轉換得以快捷的進行。
BCD碼的優勢是效率高:好比十進制要以二進制的形式在計算機中存儲,十進制直接轉換成與之對應的BCD碼比十進制經過除法取餘再轉換的效率來的高。
什麼是base64?
Base64是網絡上最多見的用於傳輸8Bit字節碼的編碼方式之一,Base64就是一種基於64個可打印字符來表示二進制數據的方法。
爲何會有base64?
因爲HTTP協議是文本協議,因此在HTTP協議下傳輸二進制數據須要將二進制數據轉換爲字符數據。然而直接轉換是不行的。由於網絡傳輸只能傳輸可打印字符。
問: 什麼是「可打印字符」呢?
答: 在ASCII碼中規定,0~3一、128這33個字符屬於控制字符,32~127這95個字符屬於可打印字符,也就是說網絡傳輸只能傳輸這95個字符,不在這個範圍內的字符沒法傳輸。
問: 那麼該怎麼才能傳輸其餘字符呢?
答: 其中一種方式就是使用Base64。Base64通常用於在HTTP協議下傳輸二進制數據。
base64實現原理
Base64的索引與對應字符的關係以下表所示:
也就是說,若是將索引轉換爲對應的二進制數據的話須要至多6個Bit(2^6=64)。然而ASCII碼須要8個Bit來表示,那麼怎麼使用6個Bit來表示8個Bit的數據呢?6個Bit固然不能存儲8個Bit的數據,可是46個Bit能夠存儲38個Bit的數據啊
能夠看到「Son」經過Base64編碼轉換成了「U29u」。這是剛恰好的狀況,3個ASCII字符恰好轉換成對應的4個Base64字符。可是,當須要轉換的字符數不是3的倍數的狀況下該怎麼辦呢?Base64規定,當須要轉換的字符不是3的倍數時,一概採用補0的方式湊足3的倍數,具體以下表所示:
每6個Bit爲一組,第一組轉換後爲字符「U」,第二組末尾補4個0轉換後爲字符「w」。剩下的使用「=」替代。即字符「S」經過Base64編碼後爲「Uw==」。這就是Base64的編碼過程。
好了,原理懂了,那麼若是要進行base64編碼,咱們該怎麼作呢?本身擼一個方法?找一個庫?都行,可是HTML規範中已經規定了base64轉換的API,window對象上能夠訪問到base64編碼和解碼的方法,直接調用便可。
window.atob() // 對base64編碼過的字符串進行解碼
window.btoa() // 對ASCII編碼的字符串進行base64編碼(不支持漢字,漢字可經過URIencode預處理後再編碼)
base64有哪些應用場景
前端將較小的icon編碼爲base64直接在文檔中加載,減小http請求
電子郵件傳輸二進制文件時,一般用base64編碼後再傳
注意:
base64編碼後的數據量是要比編碼前大的,因此base64不能用於減小數據量。
base64不能用於加密數據,即便使用私有的索引表也是不安全的。
關於轉中文出錯:btoa("中文") // The string to be encoded contains characters outside of the Latin1 range.
意思就是超出支持範圍,ASCII。
可是,若是你非要使用btoa來base64轉碼中文,也不是不行,就是略微蛋疼。以下:
btoa(escape("中文")) // "JXU0RTJEJXU2NTg3" unescape(atob("JXU0RTJEJXU2NTg3")) // "中文" btoa(encodeURI("https://zweizhao.com/文章/JS經常使用轉碼URI與Base64.md")) // "aHR0cHM6Ly96d2Vpemhhby5jb20vJUU2JTk2JTg3JUU3JUFCJUEwL0pTJUU1JUI4JUI4JUU3JTk0JUE4JUU4JUJEJUFDJUU3JUEwJTgxVVJJJUU0JUI4JThFQmFzZTY0Lm1k" decodeURI(atob("aHR0cHM6Ly96d2Vpemhhby5jb20vJUU2JTk2JTg3JUU3JUFCJUEwL0pTJUU1JUI4JUI4JUU3JTk0JUE4JUU4JUJEJUFDJUU3JUEwJTgxVVJJJUU0JUI4JThFQmFzZTY0Lm1k")) // "https://zweizhao.com/文章/JS經常使用轉碼URI與Base64.md" btoa(encodeURIComponent("https://zweizhao.com/文章/JS經常使用轉碼URI與Base64.md")) // "aHR0cHMlM0ElMkYlMkZ6d2Vpemhhby5jb20lMkYlRTYlOTYlODclRTclQUIlQTAlMkZKUyVFNSVCOCVCOCVFNyU5NCVBOCVFOCVCRCVBQyVFNyVBMCU4MVVSSSVFNCVCOCU4RUJhc2U2NC5tZA==" decodeURIComponent(atob("aHR0cHMlM0ElMkYlMkZ6d2Vpemhhby5jb20lMkYlRTYlOTYlODclRTclQUIlQTAlMkZKUyVFNSVCOCVCOCVFNyU5NCVBOCVFOCVCRCVBQyVFNyVBMCU4MVVSSSVFNCVCOCU4RUJhc2U2NC5tZA==")) // "https://zweizhao.com/文章/JS經常使用轉碼URI與Base64.md"
參考:
從base64到atob和btoa的一些理解
JS經常使用轉碼URI與Base64
每一個MIME類型由兩部分組成,前面是數據的大類別,例如聲音audio、圖象image等,後面定義具體的種類。
常見的MIME類型(通用型):
超文本標記語言文本 .html text/html
xml文檔 .xml text/xml
XHTML文檔 .xhtml application/xhtml+xml
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
PDF文檔 .pdf application/pdf
Microsoft Word文件 .word application/msword
PNG圖像 .png image/png
GIF圖形 .gif image/gif
JPEG圖形 .jpeg,.jpg image/jpeg
au聲音文件 .au audio/basic
MIDI音樂文件 mid,.midi audio/midi,audio/x-midi
RealAudio音樂文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
任意的二進制數據 application/octet-stream
URL傳輸過程?
HTTP協議中參數組件的傳輸是key=value
鍵值對的形式,若是要傳輸多個參數就須要用「&」符號對鍵值對進行分隔。例如?name1=value1&name2=$value2
,這樣在服務器收到這種字符串的時候,會用「&」分隔出每個參數,而後再用「=」來分隔出參數值。
針對name1=value1&name2=value2
咱們來講一下客戶端到服務器端的概念上解析過程:
上述字符串在計算機中用ASCII碼(16進制)表示爲:6E616D6531 3D 76616C756531 26 6E616D6532 3D 76616C756532
服務器端在接收到該數據後就能夠遍歷該字節流,首先一個字節一個字節的讀取,當讀到3D這個字節的時候,服務器端就知道前面讀到的字節串表示一個key,繼續讀取,若是遇到了26,表示從剛纔讀到的3D到26字節之間的字節串是上一個key的value,按照此方法就能夠解析出客戶端傳過來的參數。
如今又這樣一個問題:若是個人蔘數值中就包含=或者&這樣的特殊子字符的時候,該怎麼辦。好比說name1=value1
,其中value1的值是va&lu=e1
,那麼在傳輸過程當中就會變成name1=va&lu=e1
。用戶傳輸的本意是隻有一個鍵值對,可是服務器端會解析成兩個鍵值對,這樣就天然的產生了歧義。
如何解決上述問題帶來的歧義呢?解決之法就是對URL進行編碼!!!
URL編碼只是簡單的在特殊字符的各個字節(16進制)前加上」%」便可。例如,咱們對上述會產生歧義的字符(「va&lu=e1」)進行編碼後的結果:name1=va%26lu%3D
,這樣服務器會把緊跟在」%」後的字節當成普通的字節,不會把它當成各個參數或鍵值對的分隔符。
另一個問題是,爲何要用ASCII碼傳輸,可不能夠用別的編碼?
由於一些歷史的緣由URL設計者使用US-ASCII字符集表示URL。(緣由好比ASCII比較簡單;全部的系統都支持ASCII)。固然能夠用別的編碼,你能夠本身開發一套編碼而後本身進行解析。就像大部分國家都有本身的語言同樣。可是國家之間要怎麼進行交流呢,用英語吧,英語的使用範圍最廣。
一般若是同樣的東西須要編碼,就說明這樣的東西並不適合傳輸。至於緣由有多種多樣,size過大,包含隱私數據等等。對於URL來講,之全部要進行編碼,是由於URL中有些字符會引發歧義。
例如,URL參數字符串中若是包含」&」或者」%」勢必會形成服務器解析錯誤,因此須要對其進行編碼。
又如,URL的編碼格式採用的是ASCII碼而不是Unicode,這也就是說你不能在URL中包含任何非ASCII字符,好比中文。不然若是客戶端瀏覽器和服務器端瀏覽器支持的字符集不一樣的狀況下,中文可能會形成問題。
URL編碼的原則就是使用安全的字符(沒有特殊用途或者特殊意義的可打印字符)去表示那些不安全的字符。
哪些字符須要編碼
RFC3986文檔規定,URL中只容許包含英文字母(a-zA-Z)、數字(0-9)、- _ . ~4個特殊字符以及全部的保留字符。RFC3986文檔對URL的編碼解碼問題作出了詳細的建議,指出了哪些字符須要被編碼纔不會引發URL語義的轉變,以及對爲何這些字符須要編碼作出了相應的解釋。
US-ASCII字符集中沒有對應的可打印字符:URL中只容許使用可打印的字符。US-ASCII碼中的10-7F字節全都表示控制字符,這些字符不能直接出如今URL中。同時對於80-FF字節,因爲已經超出了ASCII碼定義字符的範圍,所以也不能放在URL中。
保留字符:RUL能夠劃分爲幹了組件,協議、主機、路徑等。有一些字符(: / ? # [ ] @)是用做分隔不一樣組件的。例如:冒號用於分隔協議和主機組件,斜槓用於分隔主機和路徑,問號用於分隔路徑和查詢參數,等等。還有一些字符(! $ & * + , ; =)用於在每一個組件中起到分隔做用,如等號用於表示查詢參數中的鍵值對,&符號用於分隔查詢多個鍵值對。當組件中的普通數據包含這些特殊字符時,須要對其進行編碼。
RFC3986中指定了如下字符爲保留字符: ! * ’ ( ) ; : @ & = + $ , / ? # [ ]
不安全字符:還有一些字符,當他們直接放在URL中的時候,可能會引發解析程序的歧義。這些字符被視爲不安全的字符,緣由有不少。
空格:URL在傳輸的過程,或者用戶在排版的過程當中,或者文本處理程序在處理URL的過程,都有可能引入可有可無的空格,或者將那些有意義的空格給去掉。
引號 以及 <>:引號和尖括號一般用於在普通文本中起到分隔URL的做用。
警號#:一般用於表示書籤或者錨點。
%:百分號自己用做對不安全的字符進行編碼是使用的特殊字符,所以自己須要編碼。
{ } | ^ [ ] ’ ~:某一些網關或者傳輸代理會篡改這些字符
須要注意的是,對於URL中的合法字符,編碼和不編碼是等價的,可是對於上邊提到的這些字符,若是不通過編碼,那麼它們可能會形成URL語義的不一樣。所以對於URL而言,只有普通英文字符和數字,特殊字符$ - _ . + ! * ’ ( )還有保留字符,才能出如今未經編碼的Url中,其餘字符均須要編碼以後才能出如今URL中。
可是因爲歷史緣由,目前尚存在一些不標準的編碼實現,例如對於」~」符號,雖然RFC3986文檔規定,對於波浪號~不須要進行URL編碼,可是仍是有不少老的網關或者傳輸代理會進行編碼。
如何對URL中的非法字符進行編碼?
URL編碼一般也被稱爲百分號編碼,是由於它的編碼方式很是簡單,使用%加上兩位字符———[0-9A-F]———表明一個字節的十六進制的形式。URL編碼默認使用的字符集是US-ASCII碼,例如a在US-ASCII碼中對應的字節值是0x61,那麼URL編碼以後獲得的就是%61,咱們在地址欄中輸入http://g.cn/search?q=%61%62%63,實際上就等於在google中搜索abc。又如@符號在ASCII字符集中對應的字節爲0x40,通過URL編碼以後獲得的就是%40。
對於非ASCII字符,須要使用ASCII字符集的超集進行編碼獲得相應的字節,而後對每一個字節執行百分號編碼。對於Unicode字符,RFC文檔建議使用utf-8對其進行編碼獲得相應的字節,而後對每一個字節執行百分號編碼。如」中文」使用UTF-8編碼獲得的字節是0xE4 0xB8 0xAD 0xE6 0x96 0x87,通過URL編碼以後獲得%E4%B8%AD%E6%96%87。
若是某個字符對應的ASCII字符集中的某個非保留字符,則此字節無需使用百分號表示。例如」Url編碼」,使用UTF-8編碼獲得的字節是0x55 0x72 0x6C 0xE7 0xBC 0x96 0xE7 0xA0 0x81,因爲前三個字節對應着ASCII中的非保留字符」Url」,所以這三個字節能夠用非保留字符」Url」表示。最終」Url編碼」通過編碼以後獲得的是Url%E7%BC%96%E7%A0%81,固然,若是你用%55%72%6C%E7%BC%96%E7%A0%81也是能夠的。
因爲歷史緣由,有一些Url編碼實現並不徹底遵循這樣的原則
JS中提供3個函數對URL進行編碼和解碼 escape/unescape,encodeURI/decodeURI,encodeURIComponent/decodeURIComponent。
區別
這三對函數的安全字符(即不須要編碼的字符)範圍也不一樣,以下所示:
escape(69個):/@+-._0-9a-zA-Z
encodeURI(82個):!#$&'()+,/:;=?@-._~0-9a-zA-Z
encodeURIComponent(71個):!'()*-._~0-9a-zA-Z
如今對比encodeURI和encodeURIComponent,從名稱上可看出encodeURI是針對整個URI進行編碼,咱們以特殊的URI--URL來講明下。
對於URL爲http://www.baidu.com而言,若是用encodeURI編碼,返回的還是「http://www.baidu.com」;若是用encodeURIComponent編碼,返回的爲"http%3A%2F%2Fwww.baidu.com"。
encodeURI所針對的是整個URI,並不會對分隔符如/,?,=符號進行編碼,不然破壞了URI的原有含義,而encodeURIComponent則是針對URI的
某一部分進行編碼,如查詢字符串部分的&會被轉義。
參考:爲何要進行URL編碼
關於字符編碼,來點有意思的emoji圖標:🐵, 👲🏻, 👲🏿, 💇🏽♂️, 👨👨👦👦。
看看這些可愛的小圖標,放在上個世紀,這隻能用圖片作,但如今都這些都是一個個真實的字符。感興趣的能夠研究下Emoji與Unicode、從Emoji的限制到Unicode編碼