背景:以前作 Scrapy 爬蟲,遇到一些編碼的問題,致使本身損耗了一些沒必要要的時間,仍是基礎知識不紮實呀,因此專門來整理整理,遂成此篇。javascript
這裏先釐清一些概念,方便接下來的闡述。html
字節 byte
:是計算機存儲數據的存儲單元,是一個8位的二進制數,因此最多隻能表示256個數字(0-255)前端
字符 character
:是人們經常使用的一些記號,好比」1」, 「漢」, 「お」,」℃」等等java
編碼 Encoding
:從字符到0、1二進制node
解碼 Decoding
:從0、1二進制到字符python
碼的單位:linux
1 bit(b) 比特
= 1個二進制位(0 或 1)git
1 byte(B) 字節
= 8 bgithub
1 KB 千字節 = 1024 Bweb
1 MB 兆字節 = 1024 KB
1 GB 千兆字節 = 1024 MB
1 TB 吉字節 = 1024 GB
編碼後存儲:
計算機存儲數據確定都是以二進制的方式來存儲,但從方便人們閱讀的角度上仍是分爲
文本文件
- 如 .txt
二進制文件
- 如 .png
因此文本文件和二進制文件在存儲上是沒有區別的,都是二進制。
編碼方式涉及到兩個概念:
字符集(Charset)
:字符的集合,不一樣字符集包含的字符數不一樣。
字符編碼(Character Encoding)
:字符集中字符的實際編碼方式。
一個字符集可能自己包含了字符編碼,可能對應有多種字符編碼。
以下面即將介紹的,ASCII,自己便是字符集又是字符編碼;CB2312,只是字符集,僅有一種字符編碼叫EUC-CN;而 Unicode 做爲字符集,卻對應了更多的字符編碼方式,即 UTF-八、UTF-1六、UTF-32。
ASCII
(American Standard Code for Information Interchange,美國信息交換標準代碼)是第一個 字符集/字符編碼標準。
包括:可顯示字符(英文大小寫字符、阿拉伯數字和西文符號) + 控制字符(回車鍵、退格、換行鍵等)
舉例:A = 0x41(一個字節)
Latin-1
是 ISO-8859-1 的別名。
上面介紹了美國的 ASCII,可是在歐洲,例如法語,字母上方有注音符號,它就沒法用 ASCII 碼錶示,因而歐洲人在 ASCII 的基礎上搞了 Latin-1。
相信 99% 的人第一次知道 Latin1 都是在使用 Mysq 數據庫的時候接觸到的。Latin1 是 Mysql 數據庫表的默認編碼方式,緣由之一是 Mysql 最開始是某瑞典公司開發的。
正如歐洲人拓展了 ASCII 來適應歐洲各個國家的文字,中國也同樣。
漢字數量龐大,多達10萬左右。(平常所使用的漢字只有幾千)
簡體中文常見的編碼方式是 GB2312
(中國國家標準簡體中文字符集),全稱《信息交換用漢字編碼字符集·基本集》,由中國國家標準總局發佈,1981年5月1日實施。
提出了全角/半角符號的概念。
對於人名、古漢語等方面出現的罕用字,GB2312 不能處理,這致使了後來 GBK 及 GB18030 漢字字符集的出現:
GBK
自身並不是國家標準,只是曾由國家技術監督局標準化司、電子工業部科技與質量監督司公佈爲"技術規範指導性文件"。
加入對繁體字的支持。
GB18030
,全稱:國家標準 GB18030-2005《信息技術中文編碼字符集》,是中華人民共和國現時最新的內碼字集
支持中國少數民族的文字。甚至還包括中文、日文、朝鮮語。
下面是幾種編碼方式的關係(能夠看出都是新出的兼容舊出的):
Big5
,又稱爲大五碼或五大碼,是使用繁體中文社區中最經常使用的電腦漢字字符集標準。Big5 雖普及於臺灣、香港與澳門等繁體中文通行區,但長期以來並不是當地的國家標準,而只是業界標準。
JIS
,日本經常使用編碼。
等等……
Unicode
(統一碼、萬國碼、單一碼、標準萬國碼),由非營利性的 Unicode 組織(The Unicode Consortium)所運做。
Unicode 包括 emoji。
Unicode 字符集,支持的字符編碼有 UTF-8 (經常使用、推薦)
、UTF-16
、UTF-32
。
UTF-8(8-bit Unicode Transformation Format)和 UTF-16 最大的特色,就是它們是一種變長的編碼方式。UTF-8 可使用1~5個字節表示一個符號,而 UTF-16 使用2個字節或4個字節表示;而 UTF-32 是 定長的編碼方式,用4個字節表示。
UTF-8 的具體編碼方式待寫。
舉例:UTF-8 下,英文字母只須要一個字節,漢字三個字節。
一、UTF-8 對更古怪更稀有的字符能夠用四個甚至五個字節表示,但因使用頻率低,因此空間浪費不大。
二、UTF-8 / UTF-16 這種變長的性質節省空間,很適合網絡傳輸,因此在互聯網流行的時候迅速風靡。
三、早期 UTF-16 是定長的2字節,可是後來吸取了更多字符放不下了,才改爲了變長。但改變前,Windows NT 和 Java 爲了國際化早已採用了 UTF-16。
四、UTF-8 更適合歐美人,更 UTF-16 適合中國人。由於對於歐美人來講,英文字符更常見,UTF-16 存每一個英文字符需用2個字節,而UTF-8的只須要1個字節。但對於中國人來講,漢字更常見,UTF-16 存每一箇中文字符需用2個字節,而UTF-8的須要3個字節,多佔50%。
五、UTF-32 對原本就能夠1個字節就表示的英文字母,還要用4個字節,真的太浪費了空間了。因此不多用。
單字節編碼:一個字節就表示一個字符,好比 ASCII
雙字節編碼:須要用兩個字節來表示一個字符的編碼,好比 GB2312,GBK 編碼
多字節編碼:須要用多個字節來表示一個字符的編碼,好比 Unicode/UTF-x 編碼
注意:ASCII 編碼幾乎被世界上全部編碼所兼容(UTF16和UTF32是個例外)
解碼會按照上面介紹的 字符編碼 規則來解碼,若是在 字符集 裏找不到對應的字符,則會顯示:
單字節會用 ?
代替,多字節會用 �
代替。
「�」 自己是一個合法的 Unicode 字符,碼點爲U+FFFD,對應的UTF-8編碼爲 EF BF BD。
打開 Sublime Text ,在保存文件時,有如下幾個編碼選項:
打開 VSCode,右下角也有如下幾個編碼選項:
那什麼是 LE、BE 和 BOM 呢?
字節序(byte order)
,也就是字節的順序,指的是多字節的數據在內存中的存放順序。
雖然有字節序的概念,但這僅僅是在處理多字節數據纔會存在的問題,好比double
、long
這種一個字節沒法存放的數據。
字節序分爲兩種:大端序 BE(Big Endian)
與 小端序 LE(Little Endian)
。
例如,一個「奎」的 Unicode 編碼是 594E,「乙」的 Unicode 編碼是 4E59。若是咱們收到 UTF-16 字節流爲 「594E」,那麼這是「奎」仍是「乙」呢?若是是大端序,那麼就是594E——「奎」,若是是小端序,那麼就是4E59——「乙」。
因此採用大端方式進行數據存放符合人類的正常思惟,而採用小端方式進行數據存放利於計算機處理。
大端、小端,這兩個古怪的名稱來自英國做家斯威夫特的《格列佛遊記》。在該書中,小人國裏爆發了內戰,戰爭原由是人們爭論,吃雞蛋時到底是從大頭 (Big-endian) 敲開仍是從小頭 (Little-endian) 敲開。爲了這件事情,先後爆發了六次戰爭,一個皇帝送了命,另外一個皇帝丟了王位。
網絡字節序:TCP/IP 各層協議將字節序定義爲大端序。
主機字節序:不一樣的 CPU 有不一樣的字節序。
這就牽涉出兩大 CPU 派系:
x86系列,VAX,PDP-11等處理器採用 小端序 方式存儲數據。
Motorola 6800,PowerPC 970,SPARC(除V9外)等處理器採用 大端序 方式存儲數據。
編程語言字節序:C/C++ 的字節序是依賴於編譯平臺所在的 CPU,Java 的字節序是大端序。
Unicode 的標準裏,有對 BOM
(byte order mark - 定義字節順序)的定義:若是一個文本文件的頭兩個字節是FE FF
,就表示該文件採用大端序;若是頭兩個字節是FF FE
,就表示該文件採用小端序。
U+FEFF 這個字符的名字叫作"零寬度非換行空格"(zero width no-break space),是Unicode提出來的規範。
這個在源數據裏標註的方法叫 」magic number
「,不少非文本文件也會用到,好比 bmp 文件一般會以 「42 4D」 兩字節開頭,又好比 Java 的 class 文件,則是以四字節的 「CA FE BA BE」 打頭。
只不過非文本文件僅是用來標註文件類型(因此即便丟失了拓展名依然能夠正確打開),而文本文件:
標識字節序(主要做用)
標註文件類型【例如標註採用哪一種 unicode 編碼方式】(附帶做用)
下面是 unicode 編碼裏帶 BOM 的文件開頭的表示方法:
UTF-8: EF BB BF
UTF-16 little-endian: FF FE
UTF-16 big-endian: FE FF
UTF-32 little-endian: FF FE 00 00
UTF-32 big-endian: 00 00 FE FF
注意:UTF-8 編碼不存在字節序大小端問題(由於字節序隻影響同時處理多於兩個字節的編碼方式,好比 UTF-16/UTF-32,而UTF-8是按照單字節進行處理的),因此 UTF-8 的 BOM 僅起標註文件編碼方式的做用,可加可不加。
可加可不家,這可真含糊,到底有沒有標準說,要不要給 UTF-8 加 BOM 呢?
windows 的習慣:加 BOM
微軟很早就加入了 unicode 組織對標準的制定,因此很早就實現了 BOM 這個option feature,並且也很好知足了自家操做系統對高兼容性的要求。
linux 的習慣:不加 BOM
BOM自己違反了一個UNIX設計的常見原則,就是文檔中存在的數據必須可見。BOM 不能做爲可見字符被文本編輯器編輯,就這一條不少UNIX開發者就不滿意。因此 linux 如今不會加,之後也不會加。
個人建議:若是你是 windows 的普通用戶,能夠加 BOM(例如 windows 下的 notepad ++ 默認保存格式就是 UTF-8 BOM)。但若是你是開發者,仍是別加 BOM,這樣:
一、能夠照顧各家操做系統,實現最大的兼容性
二、若是你是前端開發者,在網頁文檔裏使用 BOM 是畫蛇添足。要識別文本編碼,HTML 有 charset 屬性,XML 有 encoding 屬性,不必用 BOM 。
三、若是你是後端開發者,各類編程(腳本)語言對文本編碼的識別都有本身的一套,Python 的 # -- coding: utf-8 --,Perl 的 use utf8,都比 BOM 簡單並且可靠。
更多關於 BOM 的討論,可看:
知乎:「帶 BOM 的 UTF-8」和「無 BOM 的 UTF-8」有什麼區別?網頁代碼通常使用哪一個?
知乎:微軟爲何用帶 BOM 的 UTF-8,形成和多數系統的不兼容?
一、MacOS
中, locale
命令能夠查看計算機用戶所使用的語言,所在國家或者地區,以及當地的文化傳統所定義的一個軟件運行時的語言環境。
以個人 英文系統 和同事的 中文系統 這兩臺 macbook 分別運行 locale 命令,結果以下:
# 個人 LANG= LC_COLLATE="C" LC_CTYPE="UTF-8" LC_MESSAGES="C" LC_MONETARY="C" LC_NUMERIC="C" LC_TIME="C" LC_ALL= # 同事的 LANG="zh_CN.UTF-8" LC_COLLATE="zh_CN.UTF-8" LC_CTYPE="zh_CN.UTF-8" LC_MESSAGES="zh_CN.UTF-8" LC_MONETARY="zh_CN.UTF-8" LC_NUMERIC="zh_CN.UTF-8" LC_TIME="zh_CN.UTF-8" LC_ALL=
上面的 C 是指POSIX語言環境,它又稱C語言環境,反映了UNIX系統的歷史行爲。詳情看:https://pubs.opengroup.org/onlinepubs/009695399/xrat/xbd_chap07.html
字段解釋以下:
比較和排序習慣(LC_COLLATE)
語言符號及其分類(LC_CTYPE)
信息主要是提示信息,錯誤信息,狀態信息,標題,標籤,按鈕和菜單等(LC_MESSAGES)
貨幣單位(LC_MONETARY)
數字(LC_NUMERIC)
時間顯示格式(LC_TIME)
通常來講,這些字段要改,就改爲統一的。你不可能 LC_COLLATE 是德國的,而後LC_MONETARY 是美國的,LC_TIME 又是中國的,那就亂套了。
字段的優先級:LC_ALL > LC_* >LANG
總結:能夠看出 MacOS 默認的編碼是UTF-8。
二、在 Windows 中,默認編碼爲 GBK,而 linux 爲 UTF-8。
windows 裏查看默認編碼的方式是在 CMD 裏輸入
chcp
命令,若是結果是 936 ,即爲 GBK。
注意:例如 python 在用 open() 讀取本地文件時,若是不指定 encoding 這個參數,缺省值就是本地默認編碼
,它指的就是當前操做系統的默認編碼,若是在 windows 上,有必要的話記得指定,防止出錯。
三、編程語言裏也能夠查看操做系統的編碼,以 Python 爲例:
import locale print(locale.getdefaultlocale()) # ('en_US', 'UTF-8')
四、操做系統其實還存在 外碼
、內碼
的概念,例如 windows 其實內碼是 UTF-16,外碼纔是 GBK。但這裏涉及更深的計算機字符與編碼的知識,按下不表,之後待寫。
我經常使用的文本文件編輯工具:Sublime Text 和 VSCode 的默認編碼也是 UTF-8。
多說一句,git 的默認編碼方式也是 utf-8。 但你若是在windows 下遇到問題,能夠參考這裏的解決方案:http://howiefh.github.io/2014/10/11/git-encoding/
在用編輯器編輯好源代碼後,執行它,就會涉及到編程語言對應的編譯器編碼
。
python2 的編譯器,默認編碼是 ASCII,因此如果執行下面這段代碼,會報錯:
s = "你好,世界" print s
解決辦法就是在源代碼文件的開頭指定編碼:
# coding=utf-8 或者 # -*- coding: utf-8 -*-
這種在源代碼裏添加額外信息來標註用了什麼編碼的方法,跟上面提到的 BOM 的方法是同樣的。
但不一樣的是,由於不是二進制信息去標識的,而是用字符形式,因此瀏覽器一般會先猜想編碼來解析,等到發現了 charset設置 再更換編碼來讀取。
而 python3 則修改了默認編碼爲 UTF-8,因此再也不須要上面的聲明。
在 Python3 中, str 爲字符串類型,bytes 爲二進制類型。
如何查看 Python 編譯器默認編碼呢?
import sys print(sys.getdefaultencoding()) # utf-8
題外話:
不少設計比較早的編程語言,字符串類型其實根本就不是真的字符串,而是一串二進制數據,他的編碼跟源代碼文件的編碼保持一致,好比 C,C++,PHP,Python2。
但以後設計的編程語言就明確區分了字符串和二進制(數組)這兩種數據類型。前者跟源代碼文件的編碼保持一致,後者卻不必定。可能源代碼文件的編碼是 utf-8,即字符串也是這個編碼,可是二進制數組裏存的是以 GBK 方式編碼的數據。因此還誕生了明確標註編碼類型的二進制數組的數據類型,如 Java 使用的 UTF32 和 Go 使用的 UTF8。
Node.js 的默認編碼是 UTF-8。
Node.js 有兩個庫能夠實現編碼類型轉換:lconv
和 Iconv-lite
。
Iconv-lite 採用純 Javascripts 實現,Iconv 則經過 C++ 調用 libiconv 庫完成。前者比後者更輕量,無須編譯和處理環境依賴直接使用。在性能方面,因爲轉碼都是耗用 CPU,在 V8 的高性能下,少了 C++ 到 javascript 的層次轉換,純 Javascript 的性能比 C++ 實現得更好。
即,Iconv-lite 性能更好,推薦使用。
Iconv-lite 示例:
var iconv = require('iconv-lite'); // Convert from an encoded buffer to js string. let str = iconv.decode(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]), 'win1251'); // Convert from js string to an encoded buffer. let buf = iconv.encode("Sample input string", 'win1251'); // Check if encoding is supported iconv.encodingExists("us-ascii") // supported: // All node.js native encodings: utf8, ucs2 / utf16-le, ascii, binary, base64, hex. // Additional unicode encodings: utf16, utf16-be, utf-7, utf-7-imap, utf32, utf32-le, and utf32-be. // All widespread singlebyte encodings: Windows 125x family, ISO-8859 family, IBM/DOS codepages, Macintosh family, KOI8 family, all others supported by iconv library. Aliases like 'latin1', 'us-ascii' also supported. // All widespread multibyte encodings: CP932, CP936, CP949, CP950, GB2312, GBK, GB18030, Big5, Shift_JIS, EUC-JP.
由於 http 的數據傳輸也是二進制流,因此確定會涉及編解碼的過程。對編碼的要求,都寫在客戶端與服務器來往的 Headers 中。
Request Headers
:
Accept-Charset:瀏覽器申明本身想要接收的字符編碼方式,例如:utf-8,gb2312;q=0.7,*;q=0.3
Accept-Encoding:瀏覽器申明本身想要接收的編碼方法,一般指的是壓縮方法。例如:gzip, deflate, br
Accept-Language:瀏覽器申明本身想要接收的語言。例如:zh-CN,zh;q=0.9,en;q=0.8
上面的值都定期望的優先級排列。
Response Headers
:
Content-Type:WEB 服務器代表本身響應了什麼數據類型和字符編碼。例如:text/html; charset=utf-8
Content-Encoding:WEB 服務器代表本身使用了什麼壓縮方法。例如:gzip
Content-Language:WEB 服務器代表本身響應了什麼語言的數據。例如:zh-CN
Content-Type 是經過 Nginx 的
charset utf-8;
來配置的。
能夠看出,瀏覽器發起一個請求時所攜帶的 headers 信息只是對服務器返回資源的一種 「指望」,資源自己的類型還得用服務器應答時攜帶的 headers 信息進行表示。若是二者不一致,瀏覽器會去作一些轉碼工做。
HTML4 的聲明編碼方式:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
HTML5 的聲明編碼方式:
默認字符集就是 UTF-8。
<meta charset="UTF-8">
這種在源代碼(html 文件)裏經過添加標識數據來代表編碼類型的方式,跟上面介紹的 BOM 相似。
我記得以前的瀏覽器在頁面右鍵是能夠修改編碼方式的,但如今的 chrome 我沒有找到。能夠安裝這個拓展查看/修改當前頁面編碼:https://chrome.google.com/webstore/detail/charset/oenllhgkiiljibhfagbfogdbchhdchml
MySQL 默認編碼爲 latin1
PostgreSQL 默認編碼爲 UTF-8
MongoDB 默認編碼爲 UTF-8
檢測一個文件採用了什麼編碼,是一個很複雜的事。好在有一些工具能夠幫咱們。
用 file
命令:
> file icon.js icon.js: UTF-8 Unicode text
直接把文件拖到編輯器(Sublime 或 VSCode )裏打開,都支持自動識別編碼。
Python 裏可用 chardet 庫。
Node.js 裏可用 jschardet 庫:
var jschardet = require("jschardet") // "àíàçã" in UTF-8 jschardet.detect("\xc3\xa0\xc3\xad\xc3\xa0\xc3\xa7\xc3\xa3") // { encoding: "UTF-8", confidence: 0.9690625 } // "次經常使用國字標準字體表" in Big5 jschardet.detect("\xa6\xb8\xb1\x60\xa5\xce\xb0\xea\xa6\x72\xbc\xd0\xb7\xc7\xa6\x72\xc5\xe9\xaa\xed") // { encoding: "Big5", confidence: 0.99 }
根據 2005 年發佈的 RFC3986 關於 「百分號編碼
」規範:對 URL 中屬於 ASCII 字符集的非保留字不作編碼;對 URL 中的保留字(如參數分隔符「&」)須要取其 ASCII 內碼,而後加上「%」前綴將該字符進行替換(編碼);對於 URL 中的非 ASCII 字符(如漢字)須要取其 Unicode 內碼,而後加上「%」前綴將該字符進行替換(編碼)。
因此,URL 轉義是爲了符合規範並保證能夠被正確無誤的傳輸。
JS 裏有專門負責 URL 編碼的函數:encodeURI()
和 encodeURIComponent()
。
encodeURI('https://www.baidu.com/s?wd=你好') // "https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD" encodeURIComponent('https://www.baidu.com/s?wd=你好') // "https%3A%2F%2Fwww.baidu.com%2Fs%3Fwd%3D%E4%BD%A0%E5%A5%BD"
顧名思義,encodeURI 是用來編碼整個 URI 的。
而 encodeURIComponent 是用來編碼你準備用做 query 一部分的字符串的,例如:
手工拼URL的時候,對每對KV用 encodeURIComponent
參數傳一個回調地址時,這個 url 須要用 encodeURIComponent
早期的瀏覽器實際上是不支持的,只是如今 chrome 作了更人性化的工做。
例如,雖然你訪問了 https://www.baidu.com/s?wd=你好 ,但瀏覽器幫你偷偷作了轉義(https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD)才發送了請求,雖然你看到地址欄上仍是中文。
form 表單的 GET/POST 的 url
ajax 的 GET/POST 的 url
這些也須要作 UrlEncode,不過瀏覽器都會幫咱們去作。
早期的 IE 等一些老的瀏覽器,存在不少不作 UrlEncode,或者實現了 UrlEncode 但標準不一的狀況,好在如今開發者都無需操心了。
且如今的服務器端也基本都會幫你去自動的作 UrlDecode ,因此開發者也無需操心。
https://tool.chinaz.com/tools/urlencode.aspx
首先咱們先了解下協議的兩種方式:
二進制協議
binary protocol - 如 TCP/IP、HTTP2
文本協議
Text-based protocol - 如 SMTP、HTTP 0.九、1.0、1.1
注意,他們的區別不在於,進行網絡傳輸時,是二進制仍是文本文件(由於傳輸必定是二進制)。而在因而否是人類可讀的。
這點很像上面說到的 二進制文件 和 文本文件 的區別。本質上都是二進制文件,只是文本文件是人類可讀的。
二進制協議優勢:
數據緊湊,空間佔用小,可高效傳輸
解析簡單,性能高
方便加密
文本協議優勢:
可讀性高,也便於調試
拓展性好(而二進制協議是已經肯定的數據解析順序,很差輕易改變)
可跨處理器(聽說是因爲嚴格的內存到對象的轉換。好比,不一樣處理器架構存在數據存儲的大端小端問題)
原理:
Base64
編碼,是從二進制值到64個可打印字符的編碼。
這 64 個字符都在 ASCII 碼錶裏能找到(因此也可把 Base64 稱爲二進制值轉 ASCII )。 而 ASCII 碼太通用了,因此 Base64 的跨平臺性兼容性很強,甚至比二進制還強。
起源:
Base64 最先是用在 SMTP 協議(一種郵件傳輸協議),它是文本協議,且只支持 ASCII 字符傳遞,因此SMTP 若是要傳輸圖片、視頻這類二進制文件,用 Base64 編碼就能夠傳輸了。
早期的 HTTP 協議,也是文本協議,尤爲是 HTTP 0.9,只支持 HTML 這一種文檔。因此根本沒法傳輸圖片。因此也只能用 base64 編碼傳輸。
應用:
電子郵件的附件通常也做Base64編碼的。
在前端開發中,若是網頁有太多的圖片(尤爲是不少小圖片、小 icon 之類的),則會屢次請求服務器,影響加載速度,這時能夠給 img 標籤提供用 base64 編碼後的圖片:<img src="data:image/png;base64,iVBORw0……"/>
對證書來講,特別是根證書,通常都是Base64編碼的。
從這裏能夠看出,Base64 雖然誕生是爲了解決特定問題,可是後來的應用卻並不侷限一隅。