標籤:hex mysql 資料庫 nav var cti encoding 常用 var_dump base64編碼
引言
我相信很多人在初接觸編程時,都被字元集狠狠地虐過,特別是資料庫的中文亂碼問題,那麼亂碼是怎麼產生的呢? 我們都知道電腦是以二進位儲存和啟動並執行,那麼它是怎麼把位元據轉換為各種文字的呢? 還有我們常用的各種字元集,常用的編碼轉換,都是怎麼進行的呢?
本博文所寫的內容不是技術乾貨,只是對我們常用的字元集和編碼的一個小總結,小科普。我相信讀完本文,您應該對 字元集和常見編碼方式 有個差不多的認識了。
ASCII碼
ASCII碼(American Standard Code for Information Interchange,美國資訊交換標準代碼)應該是我們最初接觸過的編碼方式了,編程最常用的字元都被它包括在內。它使用7bit來表示 128(2e7)個字元,最高位固定為 0,共佔用一個位元組。其中:
0~31 及 127(共33個)是控制字元或通訊專用字元(其餘為可顯示字元),如控制符:TAB(定位字元)、CR(斷行符號)、DEL(刪除)、BS(退格)等,常用的ASCII值為 8、9、10 和13 分別轉換為退格、製表、換行和斷行符號字元。
48~57 為 0 到 9 十個阿拉伯數字。
65~90 為 26 個大寫英文字母,97~122 號為 26 個小寫英文字母,其餘為一些標點符號、運算子號等。
32~47,58~64,123~126 代表常用標點符號(:‘等);
我們會發現這些中很多都可以在鍵盤上可以找得到。
tips:
- PHP中我們可以使用
ord($char)來得到一個字元的ASCII碼;
- 可以用
chr($int) 來得到得到對應ASCII數值的字元;
ANSI編碼
美國人發明了電腦,並將他們最常用的字元以一個位元組存入了電腦,可是世界上這麼多的語言都要用電腦來表示怎麼辦呢?
為了使電腦支援多種語言,不同的國家和地區制定了不同的標準。而對於漢字,產生了 GB2312、 BIG5、 JIS 等各自的編碼通訊協定。這些使用 1 個位元組表示一個英文字元, 2 個位元組來代表一個字元的各種漢字延伸編碼方式,稱為 ANSI 編碼。
我們在使用window系統儲存檔案選擇編碼方式時,會看到有這個ANSI編碼這個選項,在不同的windows系統中,ANSI代表著不同的編碼。不同ANSI編碼之間互不相容,當資訊在國際間交流時,無法將屬於兩種語言的文字,儲存在同一段 ANSI 編碼的文本中。
Unicode編碼來源
既然ANSI編碼有著不同編碼之間互不相容不能共存的缺點,而現代網路中又會頻繁出現多語言互動,如果在多語言網路傳播時,一個 ‘11011011‘ 到底代表著什麼字元呢?
這時,Unicode 應運而生,它是一個足夠大的字元編碼映射表,將所有字元都囊括其中,每一個都對應唯一一個 Unicode 數值。如漢字 ‘好‘ 對應的 unicode 數值為 ‘0x597d‘, 轉為二進位為 ‘0101 1001 0111 1101‘,表示它需要 16 bit,兩個位元組,當然還有需要更多位元組來儲存的字元(原諒我舉起不來粟子)。
最新的UCS-4標準是一個尚未填充完全的31位 Unicode 字元集,它使用 31 位來儲存字元,加上恒為 0 的首位,共需佔據 32 位,4 位元組。這樣,Unicode 便能儲存 2e31 個字元,已經完全足夠儲存世界上所有的字元了。
tips:
在網路傳輸中,中文字元會被轉換為 Unicode 來傳輸,用正則匹配一個中文字元為:\x{4e00}-\x{9fa5},
PHP中想查看一個中文字元的 Unicode 碼,可以使用json_encode($str);
想 json_encode 保持原中文不自動轉為 Unicode 可以使用json_encode($str, JSON_UNESCAPED_UNICODE);添加一個 option 常量。
PHP 中各種編碼方式的轉換可以看一下我的這篇部落格:PHP用mb_string函數庫處理與windows相關中文字元
亂碼的產生就是因為對資料編碼和解碼的方式不同: windows中使用 ANSI 標準的 GBK 編碼,資料庫中使用 Unicode 的不同的編碼方式儲存,網頁瀏覽器又以不同編碼來解析,統一為 UTF-8 進行資料編碼即可解決這類問題。
注意 Unicode 只是一種符號集,字元儲存的具體實現方式看下面
UTF-8
我們知道了按照 Unicode 的標準,儲存一個字元最多要使用 4 個位元組。如果所有的字元都按照這個標準來儲存,那麼歐美國家可能要哭了,因為他們本來可以用一個位元組輕鬆儲存文檔的,因為國際化,所有的儲存空間要增大三倍。為瞭解決這個問題,UTF-8(8-bit Unicode Transformation Format)出現了。
UTF-8採用變長的編碼方式,使用 1~4 個位元組來表示一個符號:
- 對於單位元組的符號,位元組的第一位設為 0,後面 7 位為這個符號的 unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。
- 對於 n 位元組的符號(n>1),第一個位元組的前 n 位都設為 1,第 n+1 位設為0,後面位元組的前兩位一律設為 10。剩下的沒有提及的二進位位,全部為這個符號的 unicode 碼。
於是,皆大歡喜,UTF-8 成為了互連網使用最廣泛的 Unicode 編碼實現方式。
除此之外,Unicode 還有UTF-7、Punycode、CESU-8、SCSU、UTF-32、GB18030 等實現方式;
UTF8MB4
utf8mb4 並不是 Unicode 的實現方式之一,它是 mysql 的編碼方式,在最新的 mysql 中,utf8mb4 已經可以代替 utf8,並具有 utf8 不具有的特點。
mb4, 即 most bytes 4, mysql 的 utf8 編碼最多使用 3 個位元組儲存一個字元,在儲存 4 位元組字元的時候會報錯,而 utf8mb4 最多可以使用4個位元組來儲存一個字元。所以它可以用來儲存更多的 Unicode 字元,包括一些 Emoji 表情(Emoji 是一種特殊的 Unicode 編碼,常見於 ios 和 android 手機上),和很多不常用的漢字,以及任何新增的 Unicode 字元。
由於 utf8mb4 為 utf8 的超集,所以 utf8 編碼的 mysql 資料庫可以平滑過渡到 utf8mb4。
Url編碼
url 編碼是 web 開發中最常用的編碼了。由於 url 中一些字元有特殊的作用,那麼它被稱為保留字元(reserved purpose),如 = 用來賦值, ? 用來表示 query_string 的開始, # 用來標識錨點。當我們僅僅想把這些字元當作一般字元串傳輸該怎麼辦呢,這就需要使用 url 編碼。
URL編碼(URL encoding),由於其使用 % 為首碼來替代特殊字元,也被稱作百分比符號編碼,是特定內容相關的統一資源定位器 (URL)的編碼機制。也用於為 "application/x-www-form-urlencoded" MIME 準備資料, 因為它用於通過 HTTP 的請求操作 (request) 提交 HTML 表單資料。
轉換規則:
首先需要把該字元的 ASCII 的值表示為兩個十六進位的數字,然後在其前面放置逸出字元( % ),置入 URI 中的相應位置;對於非 ASCII 字元(如中文等), 需要轉換為 UTF-8 位元組序, 然後每個位元組按照上述方式表示。
下表是常見的字元和 urlencode 之後的 標識:
| char |
url |
char |
url |
char |
url |
char |
url |
char |
url |
| ! |
%21 |
# |
%23 |
$ |
%24 |
& |
%26 |
‘ |
%27 |
| ( |
%28 |
) |
%29 |
* |
%2A |
+ |
%2B |
, |
%2C |
| / |
%2F |
: |
%3A |
; |
%3B |
= |
%3D |
? |
%3F |
| @ |
%40 |
[ |
%5B |
] |
%5D |
|
|
|
|
tips: PHP中使用 urlencode() 和 urldecode() 進行 url 的編碼和解碼。
Base64編碼
base64 也是一種 web 開發中的常用編碼,它能實現簡單的可逆加密,同時在系統之間傳輸二進位等字元使用 base64 編碼也很方便。
它使用 A-Z a-z 0-9 + / 等 64 (2e6) 個字元來表示字元。嚴格來說,還有用來標識結尾處分組的位元組數的 = , 它只會出現在編碼串的最後。
編碼規則:
將一個字串以分為三個位元組(3 * 8 = 24 bit)為一個分組, 將此 24 個 bit 分為四組,每組 6 bit, 然後使用 其 6 bit 對應的十進位數來映射出一個 base64 字元;
如 UTF-8(三個位元組表示一個中文) 中文 ‘琪’ 轉 base64 的過程為
- 轉換為十六進位表示為
e790aa ;
- 每個十六進位字元轉換為4個二進位bit為
11100111 10010000 10101010;
- 拆分為四個 6 bit 分組為
111001 111001 000010 101010;
- 對應的十進位數字為
57 57 2 42 ;
- 對應 base64 編碼 為
55Cq;
十進位對應 base64 編碼的 映射表如下:
那麼一個字串拆分到最後不足三位元組了怎麼辦呢?
- 二個位元組的情況:將這二個位元組的 16 bit 分為三組,那麼最後一組只有 4 bit (16 % 6 = 4); 在這 4 個 bit 末尾添加 2 個 0 同樣湊成 6 bit;再在末尾補上一個
=號標識補位,以便於解碼;
- 一個位元組的情況:將這一個位元組一共 8 bit 分為兩組,那麼最後一組只有 2 bit (8 % 6 = 2); 在這 2 個 bit 末尾添加 4 個 0 同樣湊成 6 bit;再在末尾補上
==號標識補位,以便於解碼;
由於原來三個位元組的字元最後轉換成四個位元組來表示,base64 編碼後字串長度一般為原來 的 3/4。
以下是我為了完全瞭解 base64 編碼自己用 PHP 實現的一個 base64 編碼類別(寫完編碼犯懶了。。。):
<?phpclass Base64 { private $mapping = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘, ‘H‘, ‘I‘, ‘J‘, ‘K‘, ‘L‘, ‘M‘, ‘N‘, ‘O‘, ‘P‘, ‘Q‘, ‘R‘, ‘S‘, ‘T‘, ‘U‘, ‘V‘, ‘W‘, ‘X‘, ‘Y‘, ‘Z‘, ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘, ‘g‘, ‘h‘, ‘i‘, ‘j‘, ‘k‘, ‘l‘, ‘m‘, ‘n‘, ‘o‘, ‘p‘, ‘q‘, ‘r‘, ‘s‘, ‘t‘, ‘u‘, ‘v‘, ‘w‘, ‘x‘, ‘y‘, ‘z‘, ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘+‘, ‘/‘,]; /** * base64 主方法 * * @param $str * * @return string */ public function encode($str) { // 將字串unpack成為十六進位 $unpacked = unpack(‘H*‘, $str); $hex = str_split($unpacked[1]); $bin_str = $this->HexToBin($hex); return $this->binToBase64($bin_str); } /** * 將二進位字串分組後映射為對應的base64字串 * * @param $bin_str * * @return string */ private function binToBase64($bin_str) { $base64_str = ‘‘; $bin_list = str_split($bin_str, 6); foreach ($bin_list as $bin) { $append = ‘‘; switch (strlen($bin)) { // $bin為6位的不用特殊處理 case 6: break; // $bin為4位的是二位元組字串 2*8%6 = 4 case 4: $bin = str_pad($bin, 6, ‘0‘, STR_PAD_RIGHT); $append = ‘=‘; break; // $bin為2位的是一位元組字串 1*8%6 = 2 case 2: $append = ‘==‘; $bin = str_pad($bin, 6, ‘0‘, STR_PAD_RIGHT); break; } $order = base_convert($bin, 2, 10); $char = $this->mapping[$order]; $base64_str .= $char . $append; } return $base64_str; } /** * 將十六進位字串轉換為二進位字串 * * @param $hex * * @return string */ private function hexToBin($hex) { $bin_str = ‘‘; foreach ($hex as $char) { // 將十六進位轉為二進位字串,每個十六進位字元轉為4位二進位,不足的以0補充 $bin = base_convert($char, 16, 2); if (strlen($bin) < 4) { $bin = str_pad($bin, 4, ‘0‘, STR_PAD_LEFT); } $bin_str .= $bin; } return $bin_str; }}$encoder = new Base64();var_dump($encoder->encode(‘枕邊書blog‘)); // 5p6V6L655LmmYmxvZw==var_dump(base64_encode(‘枕邊書blog‘)); // 5p6V6L655LmmYmxvZw==
tips: 在 PHP 中使用 base64_encode() 和 base64_decode() 進行 base64 編碼和解碼。
小結
字元集和編碼一般不是 web 開發中的重點,但瞭解一下也挺有意思的,既能增長見識,還能預防哪一天突然踩了其中的坑。
如果您覺得本文對您有協助,可以幫忙點一下推薦,也可以關注我。如有錯漏之處,煩請指出,謝謝。
參考:
阮一峰:字元編碼筆記:ASCII,Unicode和UTF-8
維基百科:Unicode
Base64筆記
WEB開發中的字元集和編碼