徹底擺脫亂碼的困惑
這世上為什么要有亂碼這個東西...
先給大家出個思考題吧,一個漢字占多少字節(jié)?是不是網(wǎng)上搜出的答案五花八門,那么讀完本篇文章,我希望你至少可以準(zhǔn)確知道這個問題的答案,我覺得就算是收獲
計算機(jī)是用 0 和 1 這種二進(jìn)制形式,來表示一切信息的。所以它需要對所有的信息進(jìn)行編碼,對整數(shù)、浮點數(shù)進(jìn)行編碼,對字符串進(jìn)行編碼,對聲音、圖片、視頻進(jìn)行編碼。每一種編碼都可以深入研究來品味,比如人們發(fā)明出補(bǔ)碼來使計算機(jī)的減法變成加法,還有各種將聲音、圖片等看似不可能的媒體信息編碼成 0 和 1。一切都很美妙,唯獨字符編碼很討人厭,往往程序員們都不愿意碰,因為它實在是太亂了。
那今天就由我來幫你理一理。
一、什么是編碼
這個問題很重要
編碼是把數(shù)據(jù)從一種形式轉(zhuǎn)換為另外一種形式的過程,而解碼則是編碼的你過程。
注意,這里可沒有說計算機(jī)喲,所以編碼是一個更大的概念,比如我們每個人都有名字,那你的名字就是你這個人的一種編碼。你還有身份證號,那你的身份證號又是你的一種編碼。別小看這個簡單的例子,它能解釋你經(jīng)常混淆的兩個概念。
二、字符集與字符編碼
字符集是一個系統(tǒng)支持的所有抽象字符的集合,它是各種文字和符號的總稱,比如 ASCII 字符集、GBK 字符集,這就好比剛剛說的所有人這個集合。
字符編碼則是怎么把字符集里的這些字符一一用二進(jìn)制表示的一個字典,或者說一個函數(shù),比如 ASCll 字符編碼、GBK 字符編碼,這就好比剛剛說的名字表示法、身份證號表示法。
咦?ASCII 字符集對應(yīng)的編碼方式是 ASCII 字符編碼,GBK 字符集對應(yīng)的編碼方式是 GBK 字符編碼?沒錯,通常來說,字符集同時定義了一套同名的字符編碼規(guī)則。有人就有疑問了,那人這個字符集,不是可以用名字和身份證號兩種字符編碼么?是的,字符也可以,比如 Unicode 字符集,就可以用 UTF-8、UTF-16 等多種字符編碼來表示。
還需要注意的一點是,在計算機(jī)的世界里,不會有像名字表示法這樣,一個名字可能對應(yīng)好多人的情況,所以名字表示法在字符編碼這里,就不是一個好的字符編碼方式,自然也不會有人廣泛采用了。
三、字符編碼的起源 ASCII
世界上第一份字符集和編碼標(biāo)準(zhǔn),顯然是由美國人起草的,就是大名鼎鼎的 ASCII,一共包含了 128 個字符以及對應(yīng)的二進(jìn)制,比如小寫字母 a 對應(yīng) 01100001。這 128 個字符包括了可顯示的 26 個字母(大小寫)、10 個數(shù)字、標(biāo)點符號以及特殊的控制符,也就是英語與西歐語言中常見的字符,這 128 個字符用一個字節(jié)(可表示 256 個字符)來表示綽綽有余,所以當(dāng)時只用了 7 位,還留了一個最高位當(dāng)做奇偶校驗。不奇怪,英語國家的人覺得這真的足夠了,假如世界上所有人都用英語,那字符編碼真的超級簡潔,也就不會有什么亂碼問題和這篇文章了。
每一種字符編碼最好的描述方式就是簡單粗暴的一個字典,或者說一張表,比如 ASCII,沒什么可解釋的,它就是一張表。編碼就從右往左看,解碼就從左往右看。詳見附錄 ASCII。
四、字符編碼開始初步發(fā)展(歐洲)
EASCII:擴(kuò)展的 ASCII
我們說 ASCII 發(fā)生于美國,因為一開始只有美國有計算機(jī),所以 ASCII 足夠了。可是隨著計算機(jī)的普及,西歐等國家首先開始使用(注意這時候還沒有中國)。西歐語言的字符雖然沒有中文這么多,但有很多字符是 ASCII 表示不了的。于是他們就把 ASCII 擴(kuò)充變成了 EASCII,這擴(kuò)充的包括希臘字母、特殊的拉丁符號等。由于 ASCII 只占了 7 位,所以 EASCII 把第 8 位利用起來,仍然是一個字節(jié)來表示,這時表示的字符個數(shù)是 256。
但 EASCII 并沒有成功,西歐國家以及各個 PC 廠商各自定義出了好多不同的編碼字符集,這時候你自然就能想到,一定有一個組織站出來統(tǒng)一這個混亂的局面,制定一個標(biāo)準(zhǔn),這個組織就是國際標(biāo)準(zhǔn)化組織 ISO 及國際電工委員會 IEC。
ISO-8859
這兩個組織制定了一系列的 8 位字符集標(biāo)準(zhǔn),叫做 ISO-8859。請注意,這叫一系列,我們常說的 ISO-8859-1 才是一個字符集標(biāo)準(zhǔn),只是 ISO-8859 系列中的一個。之所以定一個系列,因為那時候還想著用單字節(jié)來編碼,但除去 ASCII 占有的 0x00~0x7F,就只剩下 0x80~0xFF 可以使用了,比如將ISO-8859-1,就是向下兼容 ASCII 的字符集標(biāo)準(zhǔn),其編碼范圍是 0x000xFF,0x000x7F 之間完全和 ASCII 一致,0x80~0x9F 之間是控制字符,0xA0~0xFF 之間是文字符號。
各個國家的符號都容納進(jìn)來顯然是不夠的,于是就分成了好多個版本,你是哪個國家的就用哪個。我們之所以經(jīng)常提到 ISO-8859-1,是因為它適用于西歐國家,而英國就是西歐國家。
以下是全部的 ISO-8859 系列:
| 標(biāo)準(zhǔn)名稱 | 別名 | 適用范圍 |
|---|---|---|
| ISO/IEC 8859-1 | Latin-1 | 西歐語言 |
| ISO/IEC 8859-2 | Latin-2 | 中歐語言 |
| ISO/IEC 8859-3 | Latin-3 | 南歐語言。世界語也可用此字符集顯示。 |
| ISO/IEC 8859-4 | Latin-4 | 北歐語言 |
| ISO/IEC 8859-5 | Cyrillic | 斯拉夫語言 |
| ISO/IEC 8859-6 | Arabic | 阿拉伯語 |
| ISO/IEC 8859-7 | Greek | 希臘語 |
| ISO/IEC 8859-8 | Hebrew | 希伯來語(視覺順序) |
| ISO/IEC 8859-8-I | Hebrew-I | 希伯來語(邏輯順序) |
| ISO/IEC 8859-9 | Latin-5 或 Turkish | 它把 Latin-1 的冰島語字母換走,加入土耳其語字母 |
| ISO/IEC 8859-10 | Latin-6 或 Nordic | 北日耳曼語支,用來代替 Latin-4 |
| ISO/IEC 8859-11 | Thai | 泰語,從泰國的 TIS620 標(biāo)準(zhǔn)字集演化而來。 |
| ISO/IEC 8859-13 | Latin-7 或 Baltic Rim | 波羅的語族 |
| ISO/IEC 8859-14 | Latin-8 或 Celtic | 凱爾特語族 |
| ISO/IEC 8859-15 | Latin-9 | 西歐語言,加入 Latin-1 欠缺的芬蘭語字母和大寫法語重音字母,以及歐元(€)符號。 |
| ISO/IEC 8859-16 | Latin-10 | 東南歐語言。主要供羅馬尼亞語使用,并加入歐元符號。 |
我們看到,ISO-8859-1 又叫做 Latin-1,所以現(xiàn)在你應(yīng)該知道,有的地方比如 MySQL 里的字符集顯示 Latin-1,不要陌生,他只是 ISO-8859-1 的別名。
還是那句話,字符編碼就是一個字典或者對照表,說什么都不如列個表實在,ISO-8859-1 的對照表(只列出擴(kuò)展了 ASCII 的部分)詳見附錄 ISO-8859-1。
五、字符編碼繼續(xù)發(fā)展(來中國了)
GB2312
計算機(jī)開水普及到了中國,于是一切就不一樣了。原本用一個字節(jié)就解決所有的符號編碼,在中國是行不通的。于是 1981 年國家標(biāo)準(zhǔn)化管理委員會定了一套字符集叫 GB2312,每個漢字符號由兩個字節(jié)組成,注意!這里變成了兩個字節(jié)。理論上它可以表示 65536 個字符,不過它只收錄了 7445 個字符,6763 個漢字和 682 個其他字符,同時它能夠兼容 ASCII。
GBK
GB2312 所收錄的漢字已經(jīng)覆蓋中國大陸 99.75% 的使用頻率,但是對一些罕見的字和繁體字還有很多少數(shù)民族使用的字符都沒法處理,于是后來就在 GB2312 的基礎(chǔ)上創(chuàng)建了一種叫 GBK 的字符編碼,GBK 不僅收錄了 27484 個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數(shù)民族文字。GBK 是利用了 GB2312 中未被使用的編碼空間上進(jìn)行擴(kuò)充,所以它能完全兼容 GB2312 和 ASCII。
BIG5
臺灣地區(qū)繁體中文標(biāo)準(zhǔn)字符集,采用雙字節(jié)編碼,共收錄 13053 個中文字,1984 年實施。
GB18030 編碼
2000 年 3 月 17 日發(fā)布的漢字編碼國家標(biāo)準(zhǔn),是對 GBK 編碼的擴(kuò)充,覆蓋中文、日文、朝鮮語和中國少數(shù)民族文字,其中收錄 27484 個漢字。GB18030 字符集采用單字節(jié)、雙字節(jié)和四字節(jié)三種方式對字符編碼。兼容 GBK 和 GB2312 字符集。
中文字符集總結(jié)
簡單總結(jié)下上面的就是,ASCII < GB2312 < GBK < GB18030,后者是前者的擴(kuò)充,也即兼容了前者。然后 BIG5 是臺灣字符集,單獨的一套。
這里拿最常用的 GBK 編碼舉例,GBK 的中文編碼是雙字節(jié)來表示的,英文編碼是用 ASCII 碼表示的,既用單字節(jié)表示。但 GBK 編碼表中也有英文字符的雙字節(jié)表示形式,所以英文字母可以有 2 種 GBK 表示方式。為區(qū)分中文,將其最高位都定成 1。英文單字節(jié)最高位都為 0。當(dāng)用 GBK 解碼時,若高字節(jié)最高位為 0,則用 ASCII 碼表解碼;若高字節(jié)最高位為 1,則用 GBK 編碼表解碼。你可以看到上圖中第一個字節(jié)在 00~7F(二進(jìn)制 00000000~01111111,所以第一位是 0) 之間的都是 ASCII。
理論上說,中文字符編碼也應(yīng)該列一個對照表,無奈它實在是太多了,不但漢字?jǐn)?shù)量多,而且編碼方式也多。所以這里只列一下數(shù)量最少的 GB2312 ,而且用鏈接的形式給你。
六、字符編碼終極發(fā)展(遍布全球)
Unicode
跟 ISO-8859 的出現(xiàn)原因一樣,只不過這次范圍擴(kuò)大到了全世界。當(dāng)然世界各國都有自己國家的字符編碼發(fā)展歷史,這里就沒必要一一展開了。1991 年,國際標(biāo)準(zhǔn)化組織和統(tǒng)一碼聯(lián)盟組織退出了 Unicode 項目,目的就是同一全世界的所有字符。
你可能知道 Unicode 分 UTF-8、UTF-16、UCS-2 等,而 ISO-8859 也分 ISO-8859-1、ISO-8859-2……你會不會覺得它們是一樣的道理呢?錯!
- ISO-8859 是一個字符集的系列,分成 ISO-8859-1、ISO-8859-2 等好多字符集,而每個字符集對應(yīng)的編碼方式就是 ISO-8859-1 編碼、ISO-8859-2 編碼,是一對一的關(guān)系。
- Unicode 本身就是一個字符集,是全世界所有字符的合集,它并不是字符集系列。而 Unicode 這個字符集特殊的地方在于,他的編碼方式不叫 Unicode 編碼,它的編碼方式有很多種,分別是 UTF-8 編碼、UTF-16 編碼等。
所以字符集系列和字符集的區(qū)別,最好的例子就是 ISO-8859;而字符集和字符編碼的區(qū)別,最好的例子就是 Unicode。
捋清了這個關(guān)系,下面我們就可以詳細(xì)說說大家心心念念的 Unicode 了。Unicode 本身并沒有規(guī)定一個字符究竟是怎么編碼,甚至都沒有規(guī)定用幾個字節(jié)表示,所以也叫可變長度字符編碼。Unicode 只規(guī)定了每個字符對應(yīng)到唯一的代碼值(code point),代碼值從 0000~10FFFF 共 1114112 個值,你曾經(jīng)看到的很討人厭的 \u0300 這種,就是 Unicode 的代碼值,這種代碼值要想變成真正存儲在機(jī)器里的字符串,一定要進(jìn)行某種編碼,如下。
ustr = \u0030;
str = ustr.encode("utf-8")
真正存儲的時候需要多少個字節(jié)是由具體的編碼格式?jīng)Q定的。比如:字符 「A」用 UTF-8 的格式編碼來存儲就只占用 1 個字節(jié),用 UTF-16 就占用 2 個字節(jié),而用 UTF-32 存儲就占用 4 個字節(jié)。而 UTF-8 本身,又不是固定長度的,也是可變長度的。
| Unicode | UTF-8 | byte 數(shù) | 備注 |
|---|---|---|---|
| 0000~007F | 0XXX XXXX | 1 | |
| 0080~07FF | 110X XXXX 10XX XXXX | 2 | |
| 0800~FFFF | 1110 XXXX 10XX XXXX 10XX XXXX | 3 | 基本定義范圍:0~FFFF |
| 1 0000~1F FFFF | 1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX | 4 | Unicode6.1 定義范圍:0~10 FFFF |
所以從這里你可以看出,一般一種編碼方式是如何兼容其他編碼的,又是如何可變長度的。UTF-8 編碼的第一位如果是 0,則只有一個字節(jié),跟 ASCII 編碼完全一樣,所以兼容了。如果是 110 開頭,則是兩個字節(jié),以此類推如上表所示。所以開頭幾位的值,是編碼本身,同時又是判斷是幾個字節(jié)數(shù)的推碼,可謂是一箭雙雕。這種設(shè)計頗有點像 CPU 中的相聯(lián)存儲器的概念。
所以如果再有人問你這個問題:UTF-8 占幾個字節(jié)?漢字到底占幾個字節(jié)?你就可以這樣專業(yè)而又不裝逼的回答:
首先漢字占幾個字節(jié)這個問題本身就不明確,應(yīng)該問 Unicode 字符集中的漢字用 UTF-8 編碼方式編碼,占幾個字節(jié)?
那這個問題就演化成了 UTF-8 占幾個字節(jié)。UTF-8 是可變長度字符編碼,所以只能說占 1~4 個字節(jié)。
- 單字節(jié)可編碼的 Unicode 范圍:\u0000\u007F(0127)
- 雙字節(jié)可編碼的 Unicode 范圍:\u0080\u07FF(1282047)
- 三字節(jié)可編碼的 Unicode 范圍:\u0800\uFFFF(204865535)
- 四字節(jié)可編碼的 Unicode 范圍:\u10000\u1FFFFF(655362097151)
那基于各種歷史的或者是合理性原因,人們把占用 1 個字節(jié)的給了 ASCII;占 2 個字節(jié)的給了拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文等;占 3 個字節(jié)的給了大部分漢字,基本等同于 GBK,含 21000 多個漢字;占 4 個字節(jié)的中日韓超大字符集里面的漢字,有 5 萬多個。
所以,漢字占三個字節(jié)這句話,應(yīng)該說成
Unicode 字符集中的大部分漢字,如果用 UTF-8 編碼的話,是占 3 個字節(jié)的。而 UTF-8 編碼本身,是 1~4 個字節(jié)的可變長度字符編碼。
七、字符編碼的總結(jié)
按時間線
- 美國線:ASCII --> EASCII --> ISO-8859 --> Unicode
- 中國線:GB2312 --> GBK --> GB18030 --> Unicode
- 他國線:... ... --> Unicode
按含義
- 字符集系列(對應(yīng) n 多個字符集):ISO-8859 系列
- 字符集(對應(yīng) n 多個字符編碼):ASCII、ISO-8859-1、GBK、Unicode
- 字符編碼:ASCII、ISO-8859-1、GBK、UTF-8、UTF-16
按字節(jié)數(shù)
- 單字節(jié):ASCII、ISO-8859 系列
- 雙字節(jié):GB2312、GBK
- 可變字節(jié):UTF-8、UTF-16
八、為什么會產(chǎn)生亂碼
前面說過,編碼和解碼,就是一個查表的過程,正著查是二進(jìn)制的值到字符,反著查是字符到二進(jìn)制的數(shù)值。所謂亂碼,就是編碼和解碼查的不是一張表嘛。就好比你叫你丈夫的爹為“公公”,你是查現(xiàn)代漢語詞典編碼出的“公公”兩個字,人家爸爸卻拿著古代漢語詞典來解碼你這個“公公”的含義,這不亂才怪嘛。
自己構(gòu)造一個亂碼很簡單,我們用 UTF-8 來編碼一個“你好”這兩個字,再用 GBK 解碼來閱讀,看看會怎么樣。
-
首先新建一個 txt 文件,就叫【亂碼.txt】吧,然后用二進(jìn)制方式打開它(我用的是 Notepad++,下載了 Hex-Editor 插件)。
-
我們查 UTF-8 編碼字典,發(fā)現(xiàn)你好兩個字的編碼是“E4BDA0”、“E5A5BD”,我們將編碼輸入。
-
好吧你說這哪是 2 進(jìn)制啊,這不是 16 進(jìn)制么。那我們就直接一點,用純 2 進(jìn)制的方式輸入,“111001001011110110100000”,“111001011010010110111101”。
- 返回正常的文本狀態(tài),選擇用 UTF-8 編碼方式查看,發(fā)現(xiàn)是正常的“你好”兩個字。但如果切換成 GBK(這里我選擇了 GB2312,一樣的),就變成了奇怪的文字。
- 這很好理解,UTF-8 編碼漢字是 3 個字節(jié),剛剛我們編碼了“你好”,對應(yīng)的字節(jié)序列是 “E4BDA0”、“E5A5BD”,一共 6 個字節(jié)。GBK 編碼漢字是 2 個字節(jié),于是解讀成了 “E4BD“、”A0E5“、”A5BD”,三個漢字。那我們查 GBK 碼表:
你有沒有發(fā)現(xiàn),居然跟我們看到的是一樣的!這還是比較有好的亂碼,起碼還能對應(yīng)上真正碼表中存在的漢字。有的亂碼字節(jié)數(shù)都不對,或者干脆碼表中查不到,就難看極了。這就要看各個不同的文本查看軟件怎么處理了,比如我 GBK 本來是雙字節(jié)的,我硬生生把一個字的后一個字節(jié)刪了,這種情況 Notepad++ 還算友好,可以讓你看到是因為少了一個字節(jié)出的問題,如果用記事本打開,就是一個大寫的 “?”。
編碼我們都理解了,就是最終寫在計算機(jī)內(nèi)存里的 01010 字節(jié)序列。而對于解碼,似乎還是有一點迷惑,解碼解成了什么呢?要相信自己的判斷,沒錯,解碼就是解成了我們眼睛看到的這些東西,他們的本質(zhì)就是屏幕上顯示的光點。比如說漢字,其實解碼我們所查的表,最終對應(yīng)的就是一個 n*n 的矩陣,最終再經(jīng)過一些列的轉(zhuǎn)換,由串口輸出到顯示屏上,矩陣中的 1 就代表有,0 就代表無,經(jīng)過放大縮小等線性變換,最終達(dá)到屏幕上的一個個小光點上,就變成了我們看到的字。老一輩的計算機(jī)從業(yè)者,有很多人力需要去造這張表,就是把每個編碼對應(yīng)的這個矩陣畫出來,最終存成字庫。如果你聽老一輩的計算機(jī)從業(yè)者講述,將聽到很多關(guān)于這里的故事,像“區(qū)位碼”,這里我就不展開說了,其實也是因為沒經(jīng)歷過,不太了解。
九、如何解決亂碼
上一環(huán)節(jié)我給你展示了一個文本文件如何產(chǎn)生了亂碼,那如何解決文本文件的編碼呢,很簡單,保存的時候用什么格式編碼保存的,讀取的時候就用什么時候讀。比如 Notepad++ 就很清晰,保存和讀取都在【編碼】這個菜單欄里。
對于記事本,我們似乎沒看到編碼這一項,其實如果你選擇另存為,一般會有四個編碼選項讓你選擇,分別是 ANSI、Unicode、Unicode big endian、UTF-8。如果你不選擇的話,默認(rèn)保存是用 ANSI,那 windows 平臺一般是指的 GBK。
這里你可能會困惑,剛剛不是說了 Unicode 不是字符集編碼,而只是字符集么,這里怎么又出現(xiàn)在編碼了。沒錯,這就是字符編碼比較亂的地方之一,命名不規(guī)范,有很多潛規(guī)則。比如這個記事本的 Unicode:Windows 平臺下默認(rèn)的 Unicode 編碼為 Little Endian 的 UTF-16,實際上它還是指的是一個具體的編碼格式,只不過被潛規(guī)則在 Windows 平臺下了,讓很多人誤以為 Unicode 就是特指某種具體的編碼方式。
瀏覽器
剛剛解釋了下記事本的亂碼解決,其實所有工具都是一樣的,只要有文本閱讀的地方,一般都會有設(shè)置編碼的地方。那么我們來看一下最常見也最容易出錯的瀏覽器。我們打開一個網(wǎng)頁發(fā)現(xiàn)是亂碼,這尤其經(jīng)常發(fā)生在我們開發(fā)測試階段,那么我們先拋離服務(wù)端,單純創(chuàng)建一個亂碼的頁面。
用 Notepad++ 寫一個以 UTF-8 編碼的文件 test.html:
<!DOCTYPE HTML>
<html>
<head></head>
<body>
你好
</body>
</html>
用 IE 瀏覽器打開,直接亂碼:
而如果我們重新用 GBK 編碼寫一個一模一樣的文件,再用 IE 打開,正常。這是為什么呢?原因很簡單,IE 瀏覽器默認(rèn)用 GBK 去解碼一個 HTML。這又是一個潛規(guī)則。
而我們怎么才能讓它顯示正確呢?你可以強(qiáng)行改瀏覽器的編碼方式,但你搞一個服務(wù),總不能讓用戶去做這個事情吧?一個個試哪個編碼方式正確?即使瀏覽器功能強(qiáng)大到可以智能分析編碼,也最好有一個標(biāo)識來告訴瀏覽器這個 HTML 是如何編碼的,這個標(biāo)識就是:
<meta charset = "utf-8"/>
把它加載 header 中:
<!DOCTYPE HTML>
<html>
<head>
<meta charset = "utf-8"/>
</head>
<body>
你好
</body>
</html>
再用 IE 瀏覽器打開,正常!
那么我們就知道了,頁面產(chǎn)生亂碼,不是我們的問題,要么是服務(wù)端沒有設(shè)置這個 meta charset,要么就是服務(wù)端設(shè)置了,但實際上編碼響應(yīng)流的時候卻用了其他編碼方式。當(dāng)然我們也能自己強(qiáng)行解決,那就是設(shè)置瀏覽器的編碼方式,不同瀏覽器設(shè)置編碼方式的位置不一樣,而且我個人感覺也很難找,不同瀏覽器的默認(rèn)編碼方式也不同,而且還有可能有 Unicode 指的是 UTF-8 這樣的潛規(guī)則。所以為了徹底杜絕這個問題,還是在 HTML 文件里面告知為好。
服務(wù)器
既然瀏覽的頁面亂碼怪服務(wù)端,那我就不得不說說服務(wù)端這邊的事。
我們寫一個 Spring Boot 程序:
@SpringBootApplication
@Controller
public class JavamateSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(JavamateSpringbootApplication.class, args);
}
@RequestMapping("hello")
@ResponseBody
public String hello() {
return "你好";
}
}
瀏覽器訪問 /hello,完美顯示“你好”,并沒有亂碼。
正因為 Spring Boot 為我們做了太多事,才這么容易發(fā)生不亂碼的情況。其實與其說為什么會亂碼,不如解釋解釋為什么這段代碼沒有亂碼。
首先沒有亂碼,一定是編解碼用的是同一套。那編碼是什么呢,這里就又涉及到潛規(guī)則了,Spring Boot 默認(rèn)情況下,@ResponseBody 會用 UTF-8 對字符串進(jìn)行編碼,而且會為響應(yīng)體設(shè)置一個相應(yīng)頭:
Content-Type: text/javascript; charset=UTF-8
解碼方面,IE 瀏覽器默認(rèn)是 GBK,但你響應(yīng)體里有這個頭了,那 IE 會改成用 UTF-8 解碼。
不知道你有沒有經(jīng)歷過 Tomcat 時代的 ISO-8859-1 亂碼的時代,那時候沒有這些強(qiáng)大的開發(fā)框架,好多地方可能要 response 直接 write 數(shù)據(jù)出去,而 Tomcat 此時的默認(rèn)編碼是 ISO-8859-1。所以那時候好多地方都需要手動改成 UTF-8。由于 UTF-8 漸漸變成了國際標(biāo)準(zhǔn),Spring Boot 框架也將內(nèi)嵌的 Tomcat 默認(rèn)編碼格式改成了 UTF-8。
那我們怎么搞出一個亂碼呢?很簡單,我們用默認(rèn)編碼格式為 ISO-8859-1 的 Response 直接 write 出去,而不依賴于框架。
@RequestMapping("hello2")
public void hello2(HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.println("你好");
writer.flush();
}
瀏覽器打開,直接亂碼。
其實更直觀的寫法應(yīng)該是這樣,拋開所有默認(rèn)的外套:
@RequestMapping("hello2")
public void hello2(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("gbk");
response.addHeader("Content-Type", "text/html");
ServletOutputStream out = response.getOutputStream();
out.write("你好".getBytes("utf-8"));
out.flush();
}
瀏覽器打開,看到了我們熟悉的亂碼。
浣犲ソ
我們看到代碼中,直接表達(dá)了:將“你好”用 UTF-8 格式編碼,并通過響應(yīng)頭告訴瀏覽器,用 GBK 的方式解碼。這自然就亂碼了。
現(xiàn)在的服務(wù)端框架,已經(jīng)達(dá)成了用 UTF-8 作為編解碼標(biāo)準(zhǔn)的共識,而且 String 的 getBytes 方法默認(rèn)的也是 UTF-8,也可以通過 file.encoding 參數(shù)配置。所以現(xiàn)在基本不用擔(dān)心亂碼問題了。在 Tomcat 亂碼滿天飛的時代,解決亂碼問題也無非是:
- 看字符串本身的編碼
- 看設(shè)置回響應(yīng)頭的編碼
- 看 HTML 文件的 meta 屬性的編碼
附錄后文末有驚喜
十、附錄
ASCII 和 ISO-8859-1 的編碼表
ASCII
| Bin | Oct | Dec | Hex | 縮寫/字符 | 解釋 |
|---|---|---|---|---|---|
| (二進(jìn)制) | (八進(jìn)制) | (十進(jìn)制) | (十六進(jìn)制) | ||
| 0000 0000 | 0 | 0 | 0x00 | NUL(null) | 空字符 |
| 0000 0001 | 1 | 1 | 0x01 | SOH(start of headline) | 標(biāo)題開始 |
| 0000 0010 | 2 | 2 | 0x02 | STX (start of text) | 正文開始 |
| 0000 0011 | 3 | 3 | 0x03 | ETX (end of text) | 正文結(jié)束 |
| 0000 0100 | 4 | 4 | 0x04 | EOT (end of transmission) | 傳輸結(jié)束 |
| 0000 0101 | 5 | 5 | 0x05 | ENQ (enquiry) | 請求 |
| 0000 0110 | 6 | 6 | 0x06 | ACK (acknowledge) | 收到通知 |
| 0000 0111 | 7 | 7 | 0x07 | BEL (bell) | 響鈴 |
| 0000 1000 | 10 | 8 | 0x08 | BS (backspace) | 退格 |
| 0000 1001 | 11 | 9 | 0x09 | HT (horizontal tab) | 水平制表符 |
| 0000 1010 | 12 | 10 | 0x0A | LF (NL line feed, new line) | 換行鍵 |
| 0000 1011 | 13 | 11 | 0x0B | VT (vertical tab) | 垂直制表符 |
| 0000 1100 | 14 | 12 | 0x0C | FF (NP form feed, new page) | 換頁鍵 |
| 0000 1101 | 15 | 13 | 0x0D | CR (carriage return) | 回車鍵 |
| 0000 1110 | 16 | 14 | 0x0E | SO (shift out) | 不用切換 |
| 0000 1111 | 17 | 15 | 0x0F | SI (shift in) | 啟用切換 |
| 0001 0000 | 20 | 16 | 0x10 | DLE (data link escape) | 數(shù)據(jù)鏈路轉(zhuǎn)義 |
| 0001 0001 | 21 | 17 | 0x11 | DC1 (device control 1) | 設(shè)備控制 1 |
| 0001 0010 | 22 | 18 | 0x12 | DC2 (device control 2) | 設(shè)備控制 2 |
| 0001 0011 | 23 | 19 | 0x13 | DC3 (device control 3) | 設(shè)備控制 3 |
| 0001 0100 | 24 | 20 | 0x14 | DC4 (device control 4) | 設(shè)備控制 4 |
| 0001 0101 | 25 | 21 | 0x15 | NAK (negative acknowledge) | 拒絕接收 |
| 0001 0110 | 26 | 22 | 0x16 | SYN (synchronous idle) | 同步空閑 |
| 0001 0111 | 27 | 23 | 0x17 | ETB (end of trans. block) | 結(jié)束傳輸塊 |
| 0001 1000 | 30 | 24 | 0x18 | CAN (cancel) | 取消 |
| 0001 1001 | 31 | 25 | 0x19 | EM (end of medium) | 媒介結(jié)束 |
| 0001 1010 | 32 | 26 | 0x1A | SUB (substitute) | 代替 |
| 0001 1011 | 33 | 27 | 0x1B | ESC (escape) | 換碼(溢出) |
| 0001 1100 | 34 | 28 | 0x1C | FS (file separator) | 文件分隔符 |
| 0001 1101 | 35 | 29 | 0x1D | GS (group separator) | 分組符 |
| 0001 1110 | 36 | 30 | 0x1E | RS (record separator) | 記錄分隔符 |
| 0001 1111 | 37 | 31 | 0x1F | US (unit separator) | 單元分隔符 |
| 0010 0000 | 40 | 32 | 0x20 | (space) | 空格 |
| 0010 0001 | 41 | 33 | 0x21 | ! | 嘆號 |
| 0010 0010 | 42 | 34 | 0x22 | " | 雙引號 |
| 0010 0011 | 43 | 35 | 0x23 | # | 井號 |
| 0010 0100 | 44 | 36 | 0x24 | $ | 美元符 |
| 0010 0101 | 45 | 37 | 0x25 | % | 百分號 |
| 0010 0110 | 46 | 38 | 0x26 | & | 和號 |
| 0010 0111 | 47 | 39 | 0x27 | ' | 閉單引號 |
| 0010 1000 | 50 | 40 | 0x28 | ( | 開括號 |
| 0010 1001 | 51 | 41 | 0x29 | ) | 閉括號 |
| 0010 1010 | 52 | 42 | 0x2A | * | 星號 |
| 0010 1011 | 53 | 43 | 0x2B | + | 加號 |
| 0010 1100 | 54 | 44 | 0x2C | , | 逗號 |
| 0010 1101 | 55 | 45 | 0x2D | - | 減號/破折號 |
| 0010 1110 | 56 | 46 | 0x2E | . | 句號 |
| 0010 1111 | 57 | 47 | 0x2F | / | 斜杠 |
| 0011 0000 | 60 | 48 | 0x30 | 0 | 字符 0 |
| 0011 0001 | 61 | 49 | 0x31 | 1 | 字符 1 |
| 0011 0010 | 62 | 50 | 0x32 | 2 | 字符 2 |
| 0011 0011 | 63 | 51 | 0x33 | 3 | 字符 3 |
| 0011 0100 | 64 | 52 | 0x34 | 4 | 字符 4 |
| 0011 0101 | 65 | 53 | 0x35 | 5 | 字符 5 |
| 0011 0110 | 66 | 54 | 0x36 | 6 | 字符 6 |
| 0011 0111 | 67 | 55 | 0x37 | 7 | 字符 7 |
| 0011 1000 | 70 | 56 | 0x38 | 8 | 字符 8 |
| 0011 1001 | 71 | 57 | 0x39 | 9 | 字符 9 |
| 0011 1010 | 72 | 58 | 0x3A | : | 冒號 |
| 0011 1011 | 73 | 59 | 0x3B | ; | 分號 |
| 0011 1100 | 74 | 60 | 0x3C | < | 小于 |
| 0011 1101 | 75 | 61 | 0x3D | = | 等號 |
| 0011 1110 | 76 | 62 | 0x3E | > | 大于 |
| 0011 1111 | 77 | 63 | 0x3F | ? | 問號 |
| 0100 0000 | 100 | 64 | 0x40 | @ | 電子郵件符號 |
| 0100 0001 | 101 | 65 | 0x41 | A | 大寫字母 A |
| 0100 0010 | 102 | 66 | 0x42 | B | 大寫字母 B |
| 0100 0011 | 103 | 67 | 0x43 | C | 大寫字母 C |
| 0100 0100 | 104 | 68 | 0x44 | D | 大寫字母 D |
| 0100 0101 | 105 | 69 | 0x45 | E | 大寫字母 E |
| 0100 0110 | 106 | 70 | 0x46 | F | 大寫字母 F |
| 0100 0111 | 107 | 71 | 0x47 | G | 大寫字母 G |
| 0100 1000 | 110 | 72 | 0x48 | H | 大寫字母 H |
| 0100 1001 | 111 | 73 | 0x49 | I | 大寫字母 I |
| 1001010 | 112 | 74 | 0x4A | J | 大寫字母 J |
| 0100 1011 | 113 | 75 | 0x4B | K | 大寫字母 K |
| 0100 1100 | 114 | 76 | 0x4C | L | 大寫字母 L |
| 0100 1101 | 115 | 77 | 0x4D | M | 大寫字母 M |
| 0100 1110 | 116 | 78 | 0x4E | N | 大寫字母 N |
| 0100 1111 | 117 | 79 | 0x4F | O | 大寫字母 O |
| 0101 0000 | 120 | 80 | 0x50 | P | 大寫字母 P |
| 0101 0001 | 121 | 81 | 0x51 | Q | 大寫字母 Q |
| 0101 0010 | 122 | 82 | 0x52 | R | 大寫字母 R |
| 0101 0011 | 123 | 83 | 0x53 | S | 大寫字母 S |
| 0101 0100 | 124 | 84 | 0x54 | T | 大寫字母 T |
| 0101 0101 | 125 | 85 | 0x55 | U | 大寫字母 U |
| 0101 0110 | 126 | 86 | 0x56 | V | 大寫字母 V |
| 0101 0111 | 127 | 87 | 0x57 | W | 大寫字母 W |
| 0101 1000 | 130 | 88 | 0x58 | X | 大寫字母 X |
| 0101 1001 | 131 | 89 | 0x59 | Y | 大寫字母 Y |
| 0101 1010 | 132 | 90 | 0x5A | Z | 大寫字母 Z |
| 0101 1011 | 133 | 91 | 0x5B | [ | 開方括號 |
| 0101 1100 | 134 | 92 | 0x5C | \ | 反斜杠 |
| 0101 1101 | 135 | 93 | 0x5D | ] | 閉方括號 |
| 0101 1110 | 136 | 94 | 0x5E | ^ | 脫字符 |
| 0101 1111 | 137 | 95 | 0x5F | _ | 下劃線 |
| 0110 0000 | 140 | 96 | 0x60 | ` | 開單引號 |
| 0110 0001 | 141 | 97 | 0x61 | a | 小寫字母 a |
| 0110 0010 | 142 | 98 | 0x62 | b | 小寫字母 b |
| 0110 0011 | 143 | 99 | 0x63 | c | 小寫字母 c |
| 0110 0100 | 144 | 100 | 0x64 | d | 小寫字母 d |
| 0110 0101 | 145 | 101 | 0x65 | e | 小寫字母 e |
| 0110 0110 | 146 | 102 | 0x66 | f | 小寫字母 f |
| 0110 0111 | 147 | 103 | 0x67 | g | 小寫字母 g |
| 0110 1000 | 150 | 104 | 0x68 | h | 小寫字母 h |
| 0110 1001 | 151 | 105 | 0x69 | i | 小寫字母 i |
| 0110 1010 | 152 | 106 | 0x6A | j | 小寫字母 j |
| 0110 1011 | 153 | 107 | 0x6B | k | 小寫字母 k |
| 0110 1100 | 154 | 108 | 0x6C | l | 小寫字母 l |
| 0110 1101 | 155 | 109 | 0x6D | m | 小寫字母 m |
| 0110 1110 | 156 | 110 | 0x6E | n | 小寫字母 n |
| 0110 1111 | 157 | 111 | 0x6F | o | 小寫字母 o |
| 0111 0000 | 160 | 112 | 0x70 | p | 小寫字母 p |
| 0111 0001 | 161 | 113 | 0x71 | q | 小寫字母 q |
| 0111 0010 | 162 | 114 | 0x72 | r | 小寫字母 r |
| 0111 0011 | 163 | 115 | 0x73 | s | 小寫字母 s |
| 0111 0100 | 164 | 116 | 0x74 | t | 小寫字母 t |
| 0111 0101 | 165 | 117 | 0x75 | u | 小寫字母 u |
| 0111 0110 | 166 | 118 | 0x76 | v | 小寫字母 v |
| 0111 0111 | 167 | 119 | 0x77 | w | 小寫字母 w |
| 0111 1000 | 170 | 120 | 0x78 | x | 小寫字母 x |
| 0111 1001 | 171 | 121 | 0x79 | y | 小寫字母 y |
| 0111 1010 | 172 | 122 | 0x7A | z | 小寫字母 z |
| 0111 1011 | 173 | 123 | 0x7B | { | 開花括號 |
| 0111 1100 | 174 | 124 | 0x7C | \ | |
| 0111 1101 | 175 | 125 | 0x7D | } | 閉花括號 |
| 0111 1110 | 176 | 126 | 0x7E | ~ | 波浪號 |
| 0111 1111 | 177 | 127 | 0x7F | DEL (delete) | 刪除 |
ISO-8859-1
| DEC | OCT | HEX | BIN | Symbol | Description |
|---|---|---|---|---|---|
| 128 | 200 | 80 | 10000000 | € | Euro sign |
| 129 | 201 | 81 | 10000001 | ||
| 130 | 202 | 82 | 10000010 | Single low-9 quotation mark | |
| 131 | 203 | 83 | 10000011 | ? | Latin small letter f with hook |
| 132 | 204 | 84 | 10000100 | ? | Double low-9 quotation mark |
| 133 | 205 | 85 | 10000101 | … | Horizontal ellipsis |
| 134 | 206 | 86 | 10000110 | ? | Dagger |
| 135 | 207 | 87 | 10000111 | ? | Double dagger |
| 136 | 210 | 88 | 10001000 | ? | Modifier letter circumflex accent |
| 137 | 211 | 89 | 10001001 | ‰ | Per mille sign |
| 138 | 212 | 8A | 10001010 | ? | Latin capital letter S with caron |
| 139 | 213 | 8B | 10001011 | ? | Single left-pointing angle quotation |
| 140 | 214 | 8C | 10001100 | ? | Latin capital ligature OE |
| 141 | 215 | 8D | 10001101 | ||
| 142 | 216 | 8E | 10001110 | ? | Latin captial letter Z with caron |
| 143 | 217 | 8F | 10001111 | ||
| 144 | 220 | 90 | 10010000 | ||
| 145 | 221 | 91 | 10010001 | Left single quotation mark | |
| 146 | 222 | 92 | 10010010 | Right single quotation mark | |
| 147 | 223 | 93 | 10010011 | “ | Left double quotation mark |
| 148 | 224 | 94 | 10010100 | ” | Right double quotation mark |
| 149 | 225 | 95 | 10010101 | ? | Bullet |
| 150 | 226 | 96 | 10010110 | – | En dash |
| 151 | 227 | 97 | 10010111 | — | Em dash |
| 152 | 230 | 98 | 10011000 | ? | Small tilde |
| 153 | 231 | 99 | 10011001 | ? | Trade mark sign |
| 154 | 232 | 9A | 10011010 | ? | Latin small letter S with caron |
| 155 | 233 | 9B | 10011011 | ? | Single right-pointing angle quotation mark |
| 156 | 234 | 9C | 10011100 | ? | Latin small ligature oe |
| 157 | 235 | 9D | 10011101 | ||
| 158 | 236 | 9E | 10011110 | ? | Latin small letter z with caron |
| 159 | 237 | 9F | 10011111 | ? | Latin capital letter Y with diaeresis |
| 160 | 240 | A0 | 10100000 | Non-breaking space | |
| 161 | 241 | A1 | 10100001 | ? | Inverted exclamation mark |
| 162 | 242 | A2 | 10100010 | ¢ | Cent sign |
| 163 | 243 | A3 | 10100011 | £ | Pound sign |
| 164 | 244 | A4 | 10100100 | ¤ | Currency sign |
| 165 | 245 | A5 | 10100101 | ¥ | Yen sign |
| 166 | 246 | A6 | 10100110 | | | Pipe, Broken vertical bar |
| 167 | 247 | A7 | 10100111 | § | Section sign |
| 168 | 250 | A8 | 10101000 | ¨ | Spacing diaeresis - umlaut |
| 169 | 251 | A9 | 10101001 | ? | Copyright sign |
| 170 | 252 | AA | 10101010 | a | Feminine ordinal indicator |
| 171 | 253 | AB | 10101011 | ? | Left double angle quotes |
| 172 | 254 | AC | 10101100 | ? | Not sign |
| 173 | 255 | AD | 10101101 | Soft hyphen | |
| 174 | 256 | AE | 10101110 | ? | Registered trade mark sign |
| 175 | 257 | AF | 10101111 | ˉ | Spacing macron - overline |
| 176 | 260 | B0 | 10110000 | ° | Degree sign |
| 177 | 261 | B1 | 10110001 | ± | Plus-or-minus sign |
| 178 | 262 | B2 | 10110010 | 2 | Superscript two - squared |
| 179 | 263 | B3 | 10110011 | 3 | Superscript three - cubed |
| 180 | 264 | B4 | 10110100 | ′ | Acute accent - spacing acute |
| 181 | 265 | B5 | 10110101 | μ | Micro sign |
| 182 | 266 | B6 | 10110110 | ? | Pilcrow sign - paragraph sign |
| 183 | 267 | B7 | 10110111 | · | Middle dot - Georgian comma |
| 184 | 270 | B8 | 10111000 | ? | Spacing cedilla |
| 185 | 271 | B9 | 10111001 | 1 | Superscript one |
| 186 | 272 | BA | 10111010 | o | Masculine ordinal indicator |
| 187 | 273 | BB | 10111011 | ? | Right double angle quotes |
| 188 | 274 | BC | 10111100 | ? | Fraction one quarter |
| 189 | 275 | BD | 10111101 | ? | Fraction one half |
| 190 | 276 | BE | 10111110 | ? | Fraction three quarters |
| 191 | 277 | BF | 10111111 | ? | Inverted question mark |
| 192 | 300 | C0 | 11000000 | à | Latin capital letter A with grave |
| 193 | 301 | C1 | 11000001 | á | Latin capital letter A with acute |
| 194 | 302 | C2 | 11000010 | ? | Latin capital letter A with circumflex |
| 195 | 303 | C3 | 11000011 | ? | Latin capital letter A with tilde |
| 196 | 304 | C4 | 11000100 | ? | Latin capital letter A with diaeresis |
| 197 | 305 | C5 | 11000101 | ? | Latin capital letter A with ring above |
| 198 | 306 | C6 | 11000110 | ? | Latin capital letter AE |
| 199 | 307 | C7 | 11000111 | ? | Latin capital letter C with cedilla |
| 200 | 310 | C8 | 11001000 | è | Latin capital letter E with grave |
| 201 | 311 | C9 | 11001001 | é | Latin capital letter E with acute |
| 202 | 312 | CA | 11001010 | ê | Latin capital letter E with circumflex |
| 203 | 313 | CB | 11001011 | ? | Latin capital letter E with diaeresis |
| 204 | 314 | CC | 11001100 | ì | Latin capital letter I with grave |
| 205 | 315 | CD | 11001101 | í | Latin capital letter I with acute |
| 206 | 316 | CE | 11001110 | ? | Latin capital letter I with circumflex |
| 207 | 317 | CF | 11001111 | ? | Latin capital letter I with diaeresis |
| 208 | 320 | D0 | 11010000 | D | Latin capital letter ETH |
| 209 | 321 | D1 | 11010001 | ? | Latin capital letter N with tilde |
| 210 | 322 | D2 | 11010010 | ò | Latin capital letter O with grave |
| 211 | 323 | D3 | 11010011 | ó | Latin capital letter O with acute |
| 212 | 324 | D4 | 11010100 | ? | Latin capital letter O with circumflex |
| 213 | 325 | D5 | 11010101 | ? | Latin capital letter O with tilde |
| 214 | 326 | D6 | 11010110 | ? | Latin capital letter O with diaeresis |
| 215 | 327 | D7 | 11010111 | × | Multiplication sign |
| 216 | 330 | D8 | 11011000 | ? | Latin capital letter O with slash |
| 217 | 331 | D9 | 11011001 | ù | Latin capital letter U with grave |
| 218 | 332 | DA | 11011010 | ú | Latin capital letter U with acute |
| 219 | 333 | DB | 11011011 | ? | Latin capital letter U with circumflex |
| 220 | 334 | DC | 11011100 | ü | Latin capital letter U with diaeresis |
| 221 | 335 | DD | 11011101 | Y | Latin capital letter Y with acute |
| 222 | 336 | DE | 11011110 | T | Latin capital letter THORN |
| 223 | 337 | DF | 11011111 | ? | Latin small letter sharp s - ess-zed |
| 224 | 340 | E0 | 11100000 | à | Latin small letter a with grave |
| 225 | 341 | E1 | 11100001 | á | Latin small letter a with acute |
| 226 | 342 | E2 | 11100010 | a | Latin small letter a with circumflex |
| 227 | 343 | E3 | 11100011 | ? | Latin small letter a with tilde |
| 228 | 344 | E4 | 11100100 | ? | Latin small letter a with diaeresis |
| 229 | 345 | E5 | 11100101 | ? | Latin small letter a with ring above |
| 230 | 346 | E6 | 11100110 | ? | Latin small letter ae |
| 231 | 347 | E7 | 11100111 | ? | Latin small letter c with cedilla |
| 232 | 350 | E8 | 11101000 | è | Latin small letter e with grave |
| 233 | 351 | E9 | 11101001 | é | Latin small letter e with acute |
| 234 | 352 | EA | 11101010 | ê | Latin small letter e with circumflex |
| 235 | 353 | EB | 11101011 | ? | Latin small letter e with diaeresis |
| 236 | 354 | EC | 11101100 | ì | Latin small letter i with grave |
| 237 | 355 | ED | 11101101 | í | Latin small letter i with acute |
| 238 | 356 | EE | 11101110 | ? | Latin small letter i with circumflex |
| 239 | 357 | EF | 11101111 | ? | Latin small letter i with diaeresis |
| 240 | 360 | F0 | 11110000 | e | Latin small letter eth |
| 241 | 361 | F1 | 11110001 | ? | Latin small letter n with tilde |
| 242 | 362 | F2 | 11110010 | ò | Latin small letter o with grave |
| 243 | 363 | F3 | 11110011 | ó | Latin small letter o with acute |
| 244 | 364 | F4 | 11110100 | ? | Latin small letter o with circumflex |
| 245 | 365 | F5 | 11110101 | ? | Latin small letter o with tilde |
| 246 | 366 | F6 | 11110110 | ? | Latin small letter o with diaeresis |
| 247 | 367 | F7 | 11110111 | ÷ | Division sign |
| 248 | 370 | F8 | 11111000 | ? | Latin small letter o with slash |
| 249 | 371 | F9 | 11111001 | ù | Latin small letter u with grave |
| 250 | 372 | FA | 11111010 | ú | Latin small letter u with acute |
| 251 | 373 | FB | 11111011 | ? | Latin small letter u with circumflex |
| 252 | 374 | FC | 11111100 | ü | Latin small letter u with diaeresis |
| 253 | 375 | FD | 11111101 | y | Latin small letter y with acute |
| 254 | 376 | FE | 11111110 | t | Latin small letter thorn |
| 255 | 377 | FF | 11111111 | ? | Latin small letter y with diaeresis |
逗你呢,沒什么驚喜。
但這樣搞大家有點于心不忍
給大家出一個題吧,1 斤 100 元的紙幣和 100 斤 1 元的紙幣,你選拿個?公眾號下期文章揭曉答案
公眾號 - 低并發(fā)編程

浙公網(wǎng)安備 33010602011771號