亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

Unicode是什么

計(jì)算機(jī)存儲(chǔ)的基本單位是 八位字節(jié) ,由 8 個(gè)比特位組成,簡(jiǎn)稱(chēng) 字節(jié) 。由于英文只由 26 個(gè)字母加若干符號(hào)組成,因此英文字符可以直接用 字節(jié) 來(lái)保存。其他諸如中日韓等語(yǔ)言,由于字符眾多,則不得不用多個(gè)字節(jié)來(lái)編碼。

隨著計(jì)算機(jī)技術(shù)的傳播,非拉丁文字符編碼技術(shù)蓬勃發(fā)展,但存在兩個(gè)比較大的局限性:

  • 不支持多語(yǔ)言 ,例如中文的編碼方案不能表示日文;
  • 沒(méi)有統(tǒng)一標(biāo)準(zhǔn) ,例如中文有 GB2312 ,GBK 、 GB18030 等多種編碼標(biāo)準(zhǔn);

由于編碼方式不統(tǒng)一,開(kāi)發(fā)人員經(jīng)常需要在不同編碼間來(lái)回轉(zhuǎn)化,錯(cuò)誤頻出。為了徹底解決這些問(wèn)題, 統(tǒng)一碼聯(lián)盟 提出了 Unicode 標(biāo)準(zhǔn)。Unicode 對(duì)世界上大部分文字系統(tǒng)進(jìn)行整理、編碼,讓計(jì)算機(jī)可以用統(tǒng)一的方式處理文本。Unicode 目前已經(jīng)收錄了超過(guò) 13 萬(wàn)個(gè)字符,天然地支持多語(yǔ)言。使用 Unicode ,即可徹底跟編碼問(wèn)題說(shuō)拜拜!

Python中的Unicode

Python 在 3 之后,str 對(duì)象內(nèi)部改用 Unicode 表示,因而被源碼稱(chēng)為 Unicode 對(duì)象。這么做好處是顯然易見(jiàn)的,程序核心邏輯統(tǒng)一用 Unicode ,只需在輸入、輸入層進(jìn)行編碼、解碼,可最大程度避免各種編碼問(wèn)題:

深度詳解Python中Unicode編碼

 

由于 Unicode 收錄字符已經(jīng)超過(guò) 13 萬(wàn)個(gè),每個(gè)字符至少需要 4 個(gè)字節(jié)來(lái)保存。這意味著巨大的內(nèi)存開(kāi)銷(xiāo),顯然是不可接受的。英文字符用 ASCII 表示僅需 1 個(gè)字節(jié),而用 Unicode 表示內(nèi)存開(kāi)銷(xiāo)卻增加 4 倍!

Python 作者們肯定不允許這樣的事情發(fā)生,不信我們先來(lái)觀察下( getsizeof 獲取對(duì)象內(nèi)存大小):

>>> import sys
# 英文字符還是1字節(jié)
>>> sys.getsizeof('ab') - sys.getsizeof('a')
1
# 中文字符需要2字節(jié)
>>> sys.getsizeof('中國(guó)') - sys.getsizeof('中')
2
# Emoji表情需要4字節(jié)
>>> sys.getsizeof('??') - sys.getsizeof('?')
4
  • 每個(gè) ASCII 英文字符,占用 1 字節(jié);
  • 每個(gè)中文字符,占用 2 字節(jié);
  • Emoji 表情,占用 4 字節(jié);

由此可見(jiàn),Python 內(nèi)部對(duì) Unicode 進(jìn)行優(yōu)化:根據(jù)文本內(nèi)容,選擇底層存儲(chǔ)單元。至于這種黑科技是怎么實(shí)現(xiàn)的,我們只能到源碼中尋找答案了。與 str 對(duì)象實(shí)現(xiàn)相關(guān)源碼如下:

  • Include/unicodeobject.h
  • Objects/unicodectype.c

在 Include/unicodeobject.h 頭文件中,我們發(fā)現(xiàn) str 對(duì)象底層存儲(chǔ)根據(jù)文本字符 Unicode 碼位范圍分成幾類(lèi):

  • PyUnicode_1BYTE_KIND ,所有字符碼位均在 U+0000 到 U+00FF 之間;
  • PyUnicode_2BYTE_KIND ,所有字符碼位均在 U+0000 到 U+FFFF 之間,且至少一個(gè)大于 U+00FF;
  • PyUnicode_4BYTE_KIND ,所有字符碼位均在 U+0000 到 U+10FFFF 之間,且至少一個(gè)大于 U+FFFF;
enum PyUnicode_Kind {
/* String contains only wstr byte characters.  This is only possible
   when the string was created with a legacy API and _PyUnicode_Ready()
   has not been called yet.  */
    PyUnicode_WCHAR_KIND = 0,
/* Return values of the PyUnicode_KIND() macro: */
    PyUnicode_1BYTE_KIND = 1,
    PyUnicode_2BYTE_KIND = 2,
    PyUnicode_4BYTE_KIND = 4
};

如果文本字符碼位均在 U+0000 到 U+00FF 之間,單個(gè)字符只需 1 字節(jié)來(lái)表示;而碼位在 U+0000 到 U+FFFF 之間的文本,單個(gè)字符則需要 2 字節(jié)才能表示;以此類(lèi)推。這樣一來(lái),根據(jù)文本碼位范圍,便可為字符選用盡量小的存儲(chǔ)單元,以最大限度節(jié)約內(nèi)存。

typedef uint32_t Py_UCS4;
typedef uint16_t Py_UCS2;
typedef uint8_t Py_UCS1;

文本類(lèi)型字符存儲(chǔ)單元字符存儲(chǔ)單元大小(字節(jié))PyUnicode_1BYTE_KINDPy_UCS11PyUnicode_2BYTE_KINDPy_UCS22PyUnicode_4BYTE_KINDPy_UCS44

Unicode 內(nèi)部存儲(chǔ)結(jié)構(gòu)因文本類(lèi)型而異,因此類(lèi)型 kind 必須作為 Unicode 對(duì)象公共字段保存。Python 內(nèi)部定義了若干個(gè) 標(biāo)志位 ,作為 Unicode 公共字段,kind 便是其中之一:

  • interned ,是否為 interned 機(jī)制維護(hù), internel 機(jī)制在本節(jié)后半部分介紹;
  • kind ,類(lèi)型,用于區(qū)分字符底層存儲(chǔ)單元大小;
  • compact ,內(nèi)存分配方式,對(duì)象與文本緩沖區(qū)是否分離,本文不涉及分離模式;
  • ascii ,文本是否均為純 ASCII ;

Objects/unicodectype.c 源文件中的 PyUnicode_New 函數(shù),根據(jù)文本字符數(shù) size 以及最大字符 maxchar 初始化 Unicode 對(duì)象。該函數(shù)根據(jù) maxchar 為 Unicode 對(duì)象選擇最緊湊的字符存儲(chǔ)單元以及底層結(jié)構(gòu)體:

maxchar < 128maxchar < 256maxchar < 65536maxchar < MAX_UNICODEkindPyUnicode_1
BYTE_KINDPyUnicode_1
BYTE_KINDPyUnicode_2
BYTE_KINDPyUnicode_4
BYTE_KINDascii1000字符存儲(chǔ)單元大小1124底層結(jié)構(gòu)體PyASCIIObjectPyCompact
UnicodeObjectPyCompact
UnicodeObjectPyCompact
UnicodeObject

PyASCIIObject

如果 str 對(duì)象保存的文本均為 ASCII ,即 maxchar<128maxchar<128,則底層由 PyASCIIObject 結(jié)構(gòu)存儲(chǔ):

/* ASCII-only strings created through PyUnicode_New use the PyASCIIObject
   structure. state.ascii and state.compact are set, and the data
   immediately follow the structure. utf8_length and wstr_length can be found
   in the length field; the utf8 pointer is equal to the data pointer. */
typedef struct {
    PyObject_HEAD
    Py_ssize_t length;          /* Number of code points in the string */
    Py_hash_t hash;             /* Hash value; -1 if not set */
    struct {
        unsigned int interned:2;
        unsigned int kind:3;
        unsigned int compact:1;
        unsigned int ascii:1;
        unsigned int ready:1;
        unsigned int :24;
    } state;
    wchar_t *wstr;              /* wchar_t representation (null-terminated) */
} PyASCIIObject;

PyASCIIObject 結(jié)構(gòu)體也是其他 Unicode 底層存儲(chǔ)結(jié)構(gòu)體的基礎(chǔ),所有字段均為 Unicode 公共字段:

  • ob_refcnt ,引用計(jì)數(shù);
  • ob_type ,對(duì)象類(lèi)型;
  • length ,文本長(zhǎng)度;
  • hash ,文本哈希值;
  • state ,Unicode 對(duì)象標(biāo)志位,包括 internel 、 kind 、 ascii 、 compact 等;
  • wstr ,略;
深度詳解Python中Unicode編碼

 

注意到,state 字段后有一個(gè) 4 字節(jié)的空洞,這是結(jié)構(gòu)體字段 內(nèi)存對(duì)齊 造成的現(xiàn)象。在 64 位機(jī)器下,指針大小為 8 字節(jié),為優(yōu)化內(nèi)存訪問(wèn)效率,wstr 必須以 8 字節(jié)對(duì)齊;而 state 字段大小只是 4 字節(jié),便留下 4 字節(jié)的空洞。PyASCIIObject 結(jié)構(gòu)體大小在 64 位機(jī)器下為 48 字節(jié),在 32 位機(jī)器下為 24 字節(jié)。

ASCII 文本則緊接著位于 PyASCIIObject 結(jié)構(gòu)體后面,以字符串對(duì)象 ‘abc’ 以及空字符串對(duì)象 ‘’ 為例:

深度詳解Python中Unicode編碼

 

注意到,與 bytes 對(duì)象一樣,Python 也在 ASCII 文本末尾,額外添加一個(gè)  字符,以兼容 C 字符串。

如此一來(lái),以 Unicode 表示的 ASCII 文本,額外內(nèi)存開(kāi)銷(xiāo)僅為 PyASCIIObject 結(jié)構(gòu)體加上末尾的  字節(jié)而已。PyASCIIObject 結(jié)構(gòu)體在 64 位機(jī)器下,大小為 48 字節(jié)。因此,長(zhǎng)度為 n 的純 ASCII 字符串對(duì)象,需要消耗 n+48+1,即 n+49 字節(jié)的內(nèi)存空間。

>>> sys.getsizeof('')
49
>>> sys.getsizeof('abc')
52
>>> sys.getsizeof('a' * 10000)
10049

PyCompactUnicodeObject

如果文本不全是 ASCII ,Unicode 對(duì)象底層便由 PyCompactUnicodeObject 結(jié)構(gòu)體保存:

/* Non-ASCII strings allocated through PyUnicode_New use the
   PyCompactUnicodeObject structure. state.compact is set, and the data
   immediately follow the structure. */
typedef struct {
    PyASCIIObject _base;
    Py_ssize_t utf8_length;     /* Number of bytes in utf8, excluding the
                                 * terminating . */
    char *utf8;                 /* UTF-8 representation (null-terminated) */
    Py_ssize_t wstr_length;     /* Number of code points in wstr, possible
                                 * surrogates count as two code points. */
} PyCompactUnicodeObject;

PyCompactUnicodeObject 在 PyASCIIObject 基礎(chǔ)上,增加 3 個(gè)字段:

  • utf8_length ,文本 UTF8 編碼長(zhǎng)度;
  • utf8 ,文本 UTF8 編碼形式,緩存以避免重復(fù)編碼運(yùn)算;
  • wstr_length ,略;
深度詳解Python中Unicode編碼

 

由于 ASCII 本身兼容 UTF8 ,無(wú)須保存 UTF8 編碼形式,這也是 ASCII 文本底層由 PyASCIIObject 保存的原因。在 64 位機(jī)器,PyCompactUnicodeObject 結(jié)構(gòu)體大小為 72 字節(jié);在 32 位機(jī)器則是 36 字節(jié)。

PyUnicode_1BYTE_KIND

如果 128<=maxchar<256128<=maxchar<256,Unicode 對(duì)象底層便由 PyCompactUnicodeObject 結(jié)構(gòu)體保存,字符存儲(chǔ)單元為 Py_UCS1 ,大小為 1 字節(jié)。以 Python® 為例,字符 ® 碼位為 U+00AE ,滿足該條件,內(nèi)部結(jié)構(gòu)如下:

深度詳解Python中Unicode編碼

 

字符存儲(chǔ)單元還是 1 字節(jié),跟 ASCII 文本一樣。 因此,Python® 對(duì)象需要占用 80 字節(jié)的內(nèi)存空間72+1*7+1=72+8=8072+1∗7+1=72+8=80:

>>> sys.getsizeof('Python®')
80

PyUnicode_2BYTE_KIND

如果 256<=maxchar<65536256<=maxchar<65536,Unicode 對(duì)象底層同樣由 PyCompactUnicodeObject 結(jié)構(gòu)體保存,但字符存儲(chǔ)單元為 Py_UCS2 ,大小為 2 字節(jié)。以 AC米蘭 為例,常用漢字碼位在 U+0100 到 U+FFFF 之間,滿足該條件,內(nèi)部結(jié)構(gòu)如下:

深度詳解Python中Unicode編碼

 

由于現(xiàn)在字符存儲(chǔ)單元為 2 字節(jié),故而 str 對(duì)象 AC米蘭 需要占用 82 字節(jié)的內(nèi)存空間:72+2*4+2=72+10=8272+2∗4+2=72+10=82

>>> sys.getsizeof('AC米蘭')
82

我們看到,當(dāng)文本包含中文后,英文字母也只能用 2 字節(jié)的存儲(chǔ)單元來(lái)保存了。

你可能會(huì)提出疑問(wèn),為什么不采用變長(zhǎng)存儲(chǔ)單元呢?例如,字母 1 字節(jié),漢字 2 字節(jié)?這是因?yàn)椴捎米冮L(zhǎng)存儲(chǔ)單元后,就無(wú)法在 O(1) 時(shí)間內(nèi)取出文本第 n 個(gè)字符了——你只能從頭遍歷直到遇到第 n 個(gè)字符。

PyUnicode_4BYTE_KIND

如果 65536<=maxchar<42949629665536<=maxchar<429496296,便只能用 4 字節(jié)存儲(chǔ)單元 Py_UCS4 了。以 AC米蘭? 為例:

深度詳解Python中Unicode編碼

 

>>> sys.getsizeof('AC米蘭')
96

這樣一來(lái),給一段英文文本加上表情,內(nèi)存暴增 4 倍,也就不奇怪了:

>>> text = 'a' * 1000
>>> sys.getsizeof(text)
1049
>>> text += '?'
>>> sys.getsizeof(text)
4080

interned機(jī)制

如果 str 對(duì)象 interned 標(biāo)識(shí)位為 1 ,Python 虛擬機(jī)將為其開(kāi)啟 interned 機(jī)制。那么,什么是 interned 機(jī)制?

先考慮以下場(chǎng)景,如果程序中有大量 User 對(duì)象,有什么可優(yōu)化的地方?

>>> class User:
...
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
...
>>>
>>> user = User(name='tom', age=20)
>>> user.__dict__
{'name': 'tom', 'age': 20}

由于對(duì)象的屬性由 dict 保存,這意味著每個(gè) User 對(duì)象都需要保存 str 對(duì)象 name 。換句話講,1 億個(gè) User 對(duì)象需要重復(fù)保存 1 億個(gè)同樣的 str 對(duì)象,這將浪費(fèi)多少內(nèi)存!

由于 str 是不可變對(duì)象,因此 Python 內(nèi)部將有潛在重復(fù)可能的字符串都做成 單例模式 ,這就是 interned 機(jī)制。Python 具體做法是在內(nèi)部維護(hù)一個(gè)全局 dict 對(duì)象,所有開(kāi)啟 interned 機(jī)制 str 對(duì)象均保存在這里;后續(xù)需要用到相關(guān)對(duì)象的地方,則優(yōu)先到全局 dict 中取,避免重復(fù)創(chuàng)建。

舉個(gè)例子,雖然 str 對(duì)象 ‘abc’ 由不同的運(yùn)算產(chǎn)生,但背后卻是同一個(gè)對(duì)象:

>>> a = 'abc'
>>> b = 'ab' + 'c'
>>> id(a), id(b), a is b
(4424345224, 4424345224, True)

分享到:
標(biāo)簽:編碼 Python Unicode
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定