日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Unicode是什么

計算機存儲的基本單位是 八位字節 ,由 8 個比特位組成,簡稱 字節 。由于英文只由 26 個字母加若干符號組成,因此英文字符可以直接用 字節 來保存。其他諸如中日韓等語言,由于字符眾多,則不得不用多個字節來編碼。

隨著計算機技術的傳播,非拉丁文字符編碼技術蓬勃發展,但存在兩個比較大的局限性:

  • 不支持多語言 ,例如中文的編碼方案不能表示日文;
  • 沒有統一標準 ,例如中文有 GB2312 ,GBK 、 GB18030 等多種編碼標準;

由于編碼方式不統一,開發人員經常需要在不同編碼間來回轉化,錯誤頻出。為了徹底解決這些問題, 統一碼聯盟 提出了 Unicode 標準。Unicode 對世界上大部分文字系統進行整理、編碼,讓計算機可以用統一的方式處理文本。Unicode 目前已經收錄了超過 13 萬個字符,天然地支持多語言。使用 Unicode ,即可徹底跟編碼問題說拜拜!

Python中的Unicode

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

深度詳解Python中Unicode編碼

 

由于 Unicode 收錄字符已經超過 13 萬個,每個字符至少需要 4 個字節來保存。這意味著巨大的內存開銷,顯然是不可接受的。英文字符用 ASCII 表示僅需 1 個字節,而用 Unicode 表示內存開銷卻增加 4 倍!

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

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

由此可見,Python 內部對 Unicode 進行優化:根據文本內容,選擇底層存儲單元。至于這種黑科技是怎么實現的,我們只能到源碼中尋找答案了。與 str 對象實現相關源碼如下:

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

在 Include/unicodeobject.h 頭文件中,我們發現 str 對象底層存儲根據文本字符 Unicode 碼位范圍分成幾類:

  • PyUnicode_1BYTE_KIND ,所有字符碼位均在 U+0000 到 U+00FF 之間;
  • PyUnicode_2BYTE_KIND ,所有字符碼位均在 U+0000 到 U+FFFF 之間,且至少一個大于 U+00FF;
  • PyUnicode_4BYTE_KIND ,所有字符碼位均在 U+0000 到 U+10FFFF 之間,且至少一個大于 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 之間,單個字符只需 1 字節來表示;而碼位在 U+0000 到 U+FFFF 之間的文本,單個字符則需要 2 字節才能表示;以此類推。這樣一來,根據文本碼位范圍,便可為字符選用盡量小的存儲單元,以最大限度節約內存。

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

文本類型字符存儲單元字符存儲單元大?。ㄗ止潱?/strong>PyUnicode_1BYTE_KINDPy_UCS11PyUnicode_2BYTE_KINDPy_UCS22PyUnicode_4BYTE_KINDPy_UCS44

Unicode 內部存儲結構因文本類型而異,因此類型 kind 必須作為 Unicode 對象公共字段保存。Python 內部定義了若干個 標志位 ,作為 Unicode 公共字段,kind 便是其中之一:

  • interned ,是否為 interned 機制維護, internel 機制在本節后半部分介紹;
  • kind ,類型,用于區分字符底層存儲單元大?。?/li>
  • compact ,內存分配方式,對象與文本緩沖區是否分離,本文不涉及分離模式;
  • ascii ,文本是否均為純 ASCII ;

Objects/unicodectype.c 源文件中的 PyUnicode_New 函數,根據文本字符數 size 以及最大字符 maxchar 初始化 Unicode 對象。該函數根據 maxchar 為 Unicode 對象選擇最緊湊的字符存儲單元以及底層結構體:

maxchar < 128maxchar < 256maxchar < 65536maxchar < MAX_UNICODEkindPyUnicode_1
BYTE_KINDPyUnicode_1
BYTE_KINDPyUnicode_2
BYTE_KINDPyUnicode_4
BYTE_KINDascii1000字符存儲單元大小1124底層結構體PyASCIIObjectPyCompact
UnicodeObjectPyCompact
UnicodeObjectPyCompact
UnicodeObject

PyASCIIObject

如果 str 對象保存的文本均為 ASCII ,即 maxchar<128maxchar<128,則底層由 PyASCIIObject 結構存儲:

/* 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 結構體也是其他 Unicode 底層存儲結構體的基礎,所有字段均為 Unicode 公共字段:

  • ob_refcnt ,引用計數;
  • ob_type ,對象類型;
  • length ,文本長度;
  • hash ,文本哈希值;
  • state ,Unicode 對象標志位,包括 internel 、 kind 、 ascii 、 compact 等;
  • wstr ,略;
深度詳解Python中Unicode編碼

 

注意到,state 字段后有一個 4 字節的空洞,這是結構體字段 內存對齊 造成的現象。在 64 位機器下,指針大小為 8 字節,為優化內存訪問效率,wstr 必須以 8 字節對齊;而 state 字段大小只是 4 字節,便留下 4 字節的空洞。PyASCIIObject 結構體大小在 64 位機器下為 48 字節,在 32 位機器下為 24 字節。

ASCII 文本則緊接著位于 PyASCIIObject 結構體后面,以字符串對象 ‘abc’ 以及空字符串對象 ‘’ 為例:

深度詳解Python中Unicode編碼

 

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

如此一來,以 Unicode 表示的 ASCII 文本,額外內存開銷僅為 PyASCIIObject 結構體加上末尾的  字節而已。PyASCIIObject 結構體在 64 位機器下,大小為 48 字節。因此,長度為 n 的純 ASCII 字符串對象,需要消耗 n+48+1,即 n+49 字節的內存空間。

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

PyCompactUnicodeObject

如果文本不全是 ASCII ,Unicode 對象底層便由 PyCompactUnicodeObject 結構體保存:

/* 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 基礎上,增加 3 個字段:

  • utf8_length ,文本 UTF8 編碼長度;
  • utf8 ,文本 UTF8 編碼形式,緩存以避免重復編碼運算;
  • wstr_length ,略;
深度詳解Python中Unicode編碼

 

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

PyUnicode_1BYTE_KIND

如果 128<=maxchar<256128<=maxchar<256,Unicode 對象底層便由 PyCompactUnicodeObject 結構體保存,字符存儲單元為 Py_UCS1 ,大小為 1 字節。以 Python® 為例,字符 ® 碼位為 U+00AE ,滿足該條件,內部結構如下:

深度詳解Python中Unicode編碼

 

字符存儲單元還是 1 字節,跟 ASCII 文本一樣。 因此,Python® 對象需要占用 80 字節的內存空間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 對象底層同樣由 PyCompactUnicodeObject 結構體保存,但字符存儲單元為 Py_UCS2 ,大小為 2 字節。以 AC米蘭 為例,常用漢字碼位在 U+0100 到 U+FFFF 之間,滿足該條件,內部結構如下:

深度詳解Python中Unicode編碼

 

由于現在字符存儲單元為 2 字節,故而 str 對象 AC米蘭 需要占用 82 字節的內存空間:72+2*4+2=72+10=8272+2∗4+2=72+10=82

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

我們看到,當文本包含中文后,英文字母也只能用 2 字節的存儲單元來保存了。

你可能會提出疑問,為什么不采用變長存儲單元呢?例如,字母 1 字節,漢字 2 字節?這是因為采用變長存儲單元后,就無法在 O(1) 時間內取出文本第 n 個字符了——你只能從頭遍歷直到遇到第 n 個字符。

PyUnicode_4BYTE_KIND

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

深度詳解Python中Unicode編碼

 

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

這樣一來,給一段英文文本加上表情,內存暴增 4 倍,也就不奇怪了:

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

interned機制

如果 str 對象 interned 標識位為 1 ,Python 虛擬機將為其開啟 interned 機制。那么,什么是 interned 機制?

先考慮以下場景,如果程序中有大量 User 對象,有什么可優化的地方?

>>> 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}

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

由于 str 是不可變對象,因此 Python 內部將有潛在重復可能的字符串都做成 單例模式 ,這就是 interned 機制。Python 具體做法是在內部維護一個全局 dict 對象,所有開啟 interned 機制 str 對象均保存在這里;后續需要用到相關對象的地方,則優先到全局 dict 中取,避免重復創建。

舉個例子,雖然 str 對象 ‘abc’ 由不同的運算產生,但背后卻是同一個對象:

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

分享到:
標簽:編碼 Python Unicode
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定