Python 字符集編碼 - UTF-8 編碼

Unicode 的編碼范圍為 0~0x10FFFF ,如此大的范圍,顯然沒辦法像 ASCII 編碼一樣使用一個(gè)字節(jié)存儲(chǔ)。為此,Unicode 制定了各種儲(chǔ)存編碼的方式,如:UTF-8、UTF-16UTF-32 ,這些存儲(chǔ)格式被稱為 Unicode 轉(zhuǎn)換格式 UTF 。

每種 Unicode 轉(zhuǎn)換格式都會(huì)把一個(gè)編碼存儲(chǔ)為一到多個(gè)編碼單元,如 UTF-8 的編碼單元為 8 位的字節(jié);UTF-16 的編碼單元為 16 位,即 2 個(gè)字節(jié);UTF-32 的編碼單元為 32 位,即 4 個(gè)字節(jié)。

其中,UTF-8 是在互聯(lián)網(wǎng)上使用最廣泛的一種 Unicode 轉(zhuǎn)換格式,具有以下顯著的優(yōu)勢。下面,我們就先來看看 UTF-8 具有哪些有點(diǎn)吧~

UTF-8 的特點(diǎn)

1. UTF-8 中每個(gè) ASCII 字符只需要一個(gè)字節(jié)去存儲(chǔ),因此一個(gè) ASCII 文本本身也是一個(gè) UTF-8 文本,即做到了向后兼容。

比如 A 的 ASCII 碼對(duì)應(yīng)為 0x41 ,a 的 ASCII 碼對(duì)應(yīng)為 0x61 ,那么 UTF-8 兼容 ASCII 也就意味著:

>> assert 'A'.encode('utf-8') == b'\x41'
>> assert 'a'.encode('utf-8') == b'\x61'
>>

這里,需要再次提醒一下:Unicode 是表現(xiàn)形式,UTF-8 是存儲(chǔ)形式;即 UTF-8 解碼之后為 Unicode ,Unicode 可以編碼成 UTF-8 。

2. UTF-8 采用字節(jié)為存儲(chǔ)單元,因此不存在字節(jié)的大端和小段的問題。

UTF-16UTF-32 的存儲(chǔ)單元分別是 2 字節(jié)和 4 字節(jié),因此在存儲(chǔ)時(shí)會(huì)涉及到大小端的問題。那什么是大小端模式呢?下面我們來暫停補(bǔ)充一下~

注:計(jì)算機(jī)中數(shù)據(jù)存儲(chǔ)以 8 bit (位)為一個(gè) byte (字節(jié)),比如 0x01 就是一個(gè)字節(jié)。但當(dāng)我們存儲(chǔ)的內(nèi)容長度不止 8 位。比如 short 為 16 位,那就需要占用兩個(gè) byte,比如 0x0001 它其實(shí)是保存在兩個(gè)位置。
其中,0x00 為高位,0x01 為低位。對(duì)于大端模式 big-endian 高位放在低地址處,低位放在高地址處;小端模式 little-endian 則是高位放在高地址處,低位放在低地址處。

關(guān)于如何獲知你的環(huán)境使用的是大端模式還是小端模式,這里有個(gè)簡單的方式:定義一個(gè) short 類型的數(shù)組即可:

>> import array
>> array.array('H', [1]).tostring()
b'\x01\x00'

數(shù)字 1short 類型中表示為 0x0001 ,高位為 0x00 ,低位為 0x01 。我們可以很直觀地看到,數(shù)組在保存數(shù)據(jù)時(shí),將高位 0x00 放在了高地址處,將低位 0x01 放在了低地址處。因此使用的就是小端模式。

那 UTF-8 為什么可以使用字節(jié)來作為存儲(chǔ)單元,而不用擔(dān)心字節(jié)序的問題呢?這就涉及到了 UTF-8 巧妙的編碼規(guī)則~

UTF-8 的編碼規(guī)則

UTF-8 最大的一個(gè)特點(diǎn),就是它是一種變長的編碼方式。它可以使用 1~4 個(gè)字節(jié)表示一個(gè)符號(hào),根據(jù)不同的符號(hào)而變化字節(jié)長度。UTF-8的編碼規(guī)則很簡單,只有二條:

1)對(duì)于單字節(jié)符號(hào),字節(jié)的第一位設(shè)為 0 ,后 7 位為這個(gè)符號(hào)的 Unicode 碼。也就是我們上文提到的向后兼容:對(duì)于英文字母,UTF-8 編碼和 ASCII 碼是相同的。

2)對(duì)于使用 X 個(gè)字節(jié)存儲(chǔ)的符號(hào),第一個(gè)字節(jié)的前 X 位設(shè)置為 1 ,第 X+1 位設(shè)置為 0 ,后面字節(jié)的前 2 位一律設(shè)置為 10 ,剩下的位置一次填充這個(gè)符號(hào)的 Unicode 碼。

下表總結(jié)了編碼規(guī)則,字母 x 表示可用于編碼的位:

Unicode 符號(hào)范圍(十六進(jìn)制) UTF-8 編碼方式 (二進(jìn)制)
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

跟據(jù)上表,解讀 UTF-8 編碼也非常簡單:如果一個(gè)字節(jié)的第一位是 0 ,則這個(gè)字節(jié)單獨(dú)就是一個(gè)字符;如果第一位是 1 ,則連續(xù)有多少個(gè) 1 ,就表示當(dāng)前字符占用多少個(gè)字節(jié)。

注:字節(jié)頭部識(shí)別就是前面的 0,110,111011110 表示字節(jié)數(shù),因此頭已經(jīng)標(biāo)出來了就不存在字節(jié)序問題了。

下面,我們就來演示一下 UTF-8 編碼的過程。

首先,獲取漢字 的 Unicode 碼:

>> ord('魚')
40060
>> bin(40060)
'0b1001110001111100'

我們不妨先對(duì) 這個(gè)漢字使用 utf-8 編碼看看使用幾個(gè)字節(jié)存儲(chǔ):

>> '魚'.encode('utf-8')
b'\xe9\xb1\xbc'

在 UTF-8 編碼中使用 3 個(gè)字節(jié)存儲(chǔ),因此其存儲(chǔ)的二進(jìn)制的形式為 1110xxxx 10xxxxxx 10xxxxxx,將 Unicode 1001 110001 111100 依次填充到占位符 x 的位置就得到:11101001 10110001 10111100。

下面,我們將上述推導(dǎo)得出的 11101001 10110001 10111100 轉(zhuǎn)換為十六進(jìn)制,驗(yàn)證一下是否為 b'\xe9\xb1\xbc'

>> hex(int('0b111010011011000110111100',2))
'0xe9b1bc'

驗(yàn)證無誤!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容