字符编码的概念(UTF-8、UTF-16、UTF-32都是什么鬼)
字符集為每個(gè)字符分配了一個(gè)唯一的編號,通過這個(gè)編號就能找到對應(yīng)的字符。在編程過程中我們經(jīng)常會使用字符,而使用字符的前提就是把字符放入內(nèi)存中,毫無疑問,放入內(nèi)存中的僅僅是字符的編號,而不是真正的字符實(shí)體。
?
這就拋出了一個(gè)問題,如何才能將字符編號放入內(nèi)存中呢?
?
對于 ASCII 字符集,這很容易。ASCII 總共包含 128 個(gè)字符,用 7 個(gè)比特位(Bit)恰好能夠存儲,不過考慮到計(jì)算機(jī)一般把字節(jié)(Byte)作為基本單元,為了操作方便,我們不妨用一個(gè)字節(jié)(也就是 8 個(gè)比特位)來存儲 ASCII。這樣雖然浪費(fèi)了一個(gè)比特位,但是讀寫效率提高了。
?
但是對于 Unicode,問題就沒有這么簡單了。Unicode 目前已經(jīng)包含了上百萬的字符,位置靠前的字符用一個(gè)字節(jié)就能存儲,位置靠后的字符用三個(gè)字節(jié)才能存儲。我們可以為所有字符都分配三個(gè)字節(jié)的內(nèi)存,也可以為編號小的字符分配一個(gè)字節(jié)或者兩個(gè)字節(jié)的內(nèi)存,而為編號大的字符分配三個(gè)字節(jié)的內(nèi)存。
?
這兩種方案各有優(yōu)缺點(diǎn),請讀者看下面的分析。
字符集和字符編碼不是一個(gè)概念,字符集定義了文字和二進(jìn)制的對應(yīng)關(guān)系,為字符分配了唯一的編號,而字符編碼規(guī)定了如何將文字的編號存儲到內(nèi)存中。有的字符集在制定時(shí)就考慮到了編碼的問題,是和編碼結(jié)合在一起的;有的字符集只管制定字符的編號,至于怎么編碼,是其他人的事情。方案1:為每個(gè)字符分配固定長度的內(nèi)存
一種方案是為每個(gè)字符分配固定長度的內(nèi)存,并且這塊內(nèi)存要足夠大,可以容納下所有的字符編號。這種方案最簡單,直接將字符編號放入內(nèi)存中即可,不需要任何轉(zhuǎn)換,并且以后在字符串中定位字符、修改字符都非常容易。
字符串就是一串連續(xù)的字符序列,它們在內(nèi)存中按次序挨著存放。在C語言中,字符串由雙引號" "包圍起來。目前的 Unicode 已經(jīng)收錄了上百萬的字符,至少需要三個(gè)字節(jié)才能容納下所有的字符編號。假設(shè)字符串"A3中¥"的 Unicode 編碼值(十六進(jìn)制形式)分別是 2A、31、DA49、BB672C,那么它們在內(nèi)存中的存儲形式為:
?
在幾乎所有的字符集中,常用字符的編號往往比較小,罕見字符的編號往往比較大,包括 Unicode 在內(nèi)。
?
A和3是 ASCII 編碼中的字符,Unicode 為了兼容 ASCII,在設(shè)計(jì)時(shí)刻意保留了原來 ASCII 中字符的編號,所以英文字母和阿拉伯?dāng)?shù)字在 Unicode 中的編號都非常小,用一個(gè)字節(jié)足以容納。中是一個(gè)漢字,編號比較大,一般要用兩個(gè)字節(jié)才能容納。¥可以看做是一個(gè)極其少見,或者只有極少數(shù)地區(qū)才會使用到的字符,這樣的字符編號往往比較大,有時(shí)候需要三個(gè)字節(jié)才能容納。
¥是人民幣符號,是漢字文化的一部分,它和其它漢字一樣,實(shí)際上是用兩個(gè)字節(jié)存儲的,不過這里我們?yōu)榱搜菔?#xff0c;故意犯錯(cuò)地說它需要三個(gè)字節(jié)。上圖中帶灰色背景的字節(jié)是沒有用到的字節(jié),它們就是被浪費(fèi)掉的一部分內(nèi)存空間,這就是用固定長度的內(nèi)存來存儲字符編號的缺點(diǎn):常用字符的編號都比較小,這種方案會浪費(fèi)很多內(nèi)存空間,對于以英文為主的國家,比如美國、加拿大、英國等,內(nèi)存利用率甚至?xí)陀?50%。
方案2:為每個(gè)字符分配盡量少的內(nèi)存
既然上面的方案有缺點(diǎn),那我們就來改進(jìn)一下。改進(jìn)的思路也很明確,就是把空閑的內(nèi)存壓縮掉,為每個(gè)字符分配盡量少的字節(jié),例如,A和3分配一個(gè)字節(jié)足以,中分配兩個(gè)字節(jié)足以,如下圖所示:
這樣雖然沒有了空閑字節(jié),不浪費(fèi)任何內(nèi)存空間了,但是又出現(xiàn)新的問題了:如果我不告訴你,你怎么知道2A表示一個(gè)字符,而不是2A31或者2A31DA才表示一個(gè)字符呢?后面的字符也有類似的問題。
?
對于第一種方案,每個(gè)字符占用的字節(jié)數(shù)是固定的,很容易區(qū)分各個(gè)字符;而這種方案,不同的字符占用的字節(jié)數(shù)不同,字符之間也沒有特殊的標(biāo)記,計(jì)算機(jī)是無法定位字符的。
?
這種方案還需要改進(jìn),必須讓不同的字符編碼有不同的特征,并且字符處理程序也需要調(diào)整,要根據(jù)這些特征去識別不同的字符。
?
要想讓不同的字符編碼有不同的特征,可以從兩個(gè)方面下手:
1) 一是從字符集本身下手,在設(shè)計(jì)字符集時(shí),刻意讓不同的字符編號有不同的特征。
?
例如,對于編號較小的、用一個(gè)字節(jié)足以容納的字符,我們就可以規(guī)定這個(gè)字符編號的最高位(Bit)必須是 0;對于編號較大的、要用兩個(gè)字節(jié)存儲的字符,我們就可以規(guī)定這個(gè)字符編號的高字節(jié)的最高位必須是 1,低字節(jié)的最高位必須是 0;對于編號更大的、需要三個(gè)字節(jié)存儲的字符,我們就可以規(guī)定這個(gè)字符編號的所有字節(jié)的最高位都必須是 1。
?
程序在定位字符時(shí),從前往后依次掃描,如果發(fā)現(xiàn)當(dāng)前字節(jié)的最高位是 0,那么就把這一個(gè)字節(jié)作為一個(gè)字符編號。如果發(fā)現(xiàn)當(dāng)前字節(jié)的最高位是 1,那么就繼續(xù)往后掃描,如果后續(xù)字節(jié)的最高位是 0,那么就把這兩個(gè)字節(jié)作為一個(gè)字符編號;如果后續(xù)字節(jié)的最高位是 1,那么就把挨著的三個(gè)字節(jié)作為一個(gè)字符編號。
?
這種方案的缺點(diǎn)很明顯,它會導(dǎo)致字符集不連續(xù),中間留出大量空白區(qū)域,這些空白區(qū)域不能定義任何字符。
?
2) 二是從字符編號下手,可以設(shè)計(jì)一種轉(zhuǎn)換方案,字符編號在存儲之前先轉(zhuǎn)換為有特征的、容易定位的編號,讀取時(shí)再按照相反的過程轉(zhuǎn)換成字符本來的編號。
?
那么,轉(zhuǎn)換后的編號要具備什么樣的特征呢?其實(shí)也可以像上面一樣,根據(jù)字節(jié)的最高位是 0 還是 1 來判斷字符到底占用了幾個(gè)字節(jié)。
?
相比第一種方案,這種方案有缺點(diǎn)也有優(yōu)點(diǎn):
?
- 缺點(diǎn)就是多了轉(zhuǎn)換過程,字符在存儲和讀取時(shí)要經(jīng)過轉(zhuǎn)換,效率低;
- 優(yōu)點(diǎn)就是在制定字符集時(shí)不用考慮存儲的問題,可以任意排布字符。
?
Unicode 到底使用哪種編碼方案
Unicode 是一個(gè)獨(dú)立的字符集,它并不是和編碼綁定的,你可以采用第一種方案,為每個(gè)字符分配固定長度的內(nèi)存,也可以采用第二種方案,為每個(gè)字符分配盡量少的內(nèi)存。
?
需要注意的是,Unicode 只是一個(gè)字符集,在制定的時(shí)候并沒有考慮編碼的問題,所以采用第二種方案時(shí),就不能從字符集本身下手了,只能從字符編號下手,這樣在存儲和讀取時(shí)都要進(jìn)行適當(dāng)?shù)霓D(zhuǎn)換。
?
Unicode 可以使用的編碼有三種,分別是:
- UFT-8:一種變長的編碼方案,使用 1~6 個(gè)字節(jié)來存儲;
- UFT-32:一種固定長度的編碼方案,不管字符編號大小,始終使用 4 個(gè)字節(jié)來存儲;
- UTF-16:介于 UTF-8 和 UTF-32 之間,使用 2 個(gè)或者 4 個(gè)字節(jié)來存儲,長度既固定又可變。
?
UTF 是 Unicode Transformation Format 的縮寫,意思是“Unicode轉(zhuǎn)換格式”,后面的數(shù)字表明至少使用多少個(gè)比特位(Bit)來存儲字符。
1) UTF-8
UTF-8 的編碼規(guī)則很簡單:如果只有一個(gè)字節(jié),那么最高的比特位為 0;如果有多個(gè)字節(jié),那么第一個(gè)字節(jié)從最高位開始,連續(xù)有幾個(gè)比特位的值為 1,就使用幾個(gè)字節(jié)編碼,剩下的字節(jié)均以 10 開頭。
?
具體的表現(xiàn)形式為:
- 0xxxxxxx:單字節(jié)編碼形式,這和 ASCII 編碼完全一樣,因此 UTF-8 是兼容 ASCII 的;
- 110xxxxx 10xxxxxx:雙字節(jié)編碼形式;
- 1110xxxx 10xxxxxx 10xxxxxx:三字節(jié)編碼形式;
- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字節(jié)編碼形式。
?
xxx 就用來存儲 Unicode 中的字符編號。
?
下面是一些字符的編碼實(shí)例(綠色部分表示本來的 Unicode 編號):
?
| 01001110 | 11100110 | 00101110 11101100 |
| 4E | E6 | 2E EC |
| 01001110 | 11000011?10100110 | 11100010?10111011?10101100 |
| 4E | C3 A6 | E2 BB AC |
對于常用的字符,它的 Unicode 編號范圍是 0 ~ FFFF,用 1~3 個(gè)字節(jié)足以存儲,只有及其罕見,或者只有少數(shù)地區(qū)使用的字符才需要 4~6個(gè)字節(jié)存儲。
2) UTF-32
UTF-32 是固定長度的編碼,始終占用 4 個(gè)字節(jié),足以容納所有的 Unicode 字符,所以直接存儲 Unicode 編號即可,不需要任何編碼轉(zhuǎn)換。浪費(fèi)了空間,提高了效率。
3) UTF-16
UFT-16 比較奇葩,它使用 2 個(gè)或者 4 個(gè)字節(jié)來存儲。
?
對于 Unicode 編號范圍在 0 ~ FFFF 之間的字符,UTF-16 使用兩個(gè)字節(jié)存儲,并且直接存儲 Unicode 編號,不用進(jìn)行編碼轉(zhuǎn)換,這跟 UTF-32 非常類似。
?
對于 Unicode 編號范圍在 10000~10FFFF 之間的字符,UTF-16 使用四個(gè)字節(jié)存儲,具體來說就是:將字符編號的所有比特位分成兩部分,較高的一些比特位用一個(gè)值介于 D800~DBFF 之間的雙字節(jié)存儲,較低的一些比特位(剩下的比特位)用一個(gè)值介于 DC00~DFFF 之間的雙字節(jié)存儲。
?
如果你不理解什么意思,請看下面的表格:
?
| 0000 0000 ~ 0000 FFFF | xxxxxxxx xxxxxxxx | xxxxxxxx xxxxxxxx | 2 |
| 0001 0000---0010 FFFF | yyyy yyyy yyxx xxxx xxxx | 110110yy yyyyyyyy 110111xx xxxxxxxx | 4 |
?
位于 D800~0xDFFF 之間的 Unicode 編碼是特別為四字節(jié)的 UTF-16 編碼預(yù)留的,所以不應(yīng)該在這個(gè)范圍內(nèi)指定任何字符。如果你真的去查看 Unicode 字符集,會發(fā)現(xiàn)這個(gè)區(qū)間內(nèi)確實(shí)沒有收錄任何字符。
?
?
UTF-16 要求在制定 Unicode 字符集時(shí)必須考慮到編碼問題,所以真正的 Unicode 字符集也不是隨意編排字符的。
總結(jié)
只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因?yàn)樗鼈儧]有單字節(jié)編碼。
?
如果你希望查看完整的 Unicode 字符集,以及各種編碼方式,請猛擊:https://unicode-table.com/cn/
雖然這個(gè)網(wǎng)站有時(shí)候無法訪問,但它是最好的一個(gè)查看 Unicode 字符集的網(wǎng)站。GB2312、Shift-JIS 等國家(地區(qū))字符集怎么編碼
GB2312、GBK、Shift-JIS 等特定國家的字符集都是在 ASCII 的基礎(chǔ)上發(fā)展起來的,它們都兼容 ASCII,所以只能采用變長的編碼方案:用一個(gè)字節(jié)存儲 ASCII 字符,用多個(gè)字節(jié)存儲本國字符。
?
以 GB2312 為例,該字符集收錄的字符較少,所以使用 1~2 個(gè)字節(jié)編碼。
- 對于 ASCII 字符,使用一個(gè)字節(jié)存儲,并且該字節(jié)的最高位是 0;
- 對于中國的字符,使用兩個(gè)字節(jié)存儲,并且規(guī)定每個(gè)字節(jié)的最高位都是 1。
?
?
由于單字節(jié)和雙字節(jié)的最高位不一樣,所以很容易區(qū)分一個(gè)字符到底用了幾個(gè)字節(jié)。
寬字符和窄字符(多字節(jié)字符)
有的編碼方式采用 1~n 個(gè)字節(jié)存儲,是變長的,例如 UTF-8、GB2312、GBK 等;如果一個(gè)字符使用了這種編碼方式,我們就將它稱為多字節(jié)字符,或者窄字符。
?
有的編碼方式是固定長度的,不管字符編號大小,始終采用 n 個(gè)字節(jié)存儲,例如 UTF-32、UTF-16 等;如果一個(gè)字符使用了這種編碼方式,我們就將它稱為寬字符。
?
Unicode 字符集可以使用窄字符的方式存儲,也可以使用寬字符的方式存儲;GB2312、GBK、Shift-JIS 等國家編碼一般都使用窄字符的方式存儲;ASCII 只有一個(gè)字節(jié),無所謂窄字符和寬字符。
總結(jié)
以上是生活随笔為你收集整理的字符编码的概念(UTF-8、UTF-16、UTF-32都是什么鬼)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webdav服务器文件大小限制,WebD
- 下一篇: 什么是原创?独立完成就是原创吗?