Python3 str在內存中的存儲方式?

很好奇,python3 的str在內存中是以幾個位元組進行存儲的呢?

首先,如果是2個位元組的話,有些unicode的code points都超出了16個bits,超出BMP的unicode就無法存儲了?

如果是4個位元組的話,這樣在內存的開銷也有點太大了吧?


這種問題就應該去看源碼。就算不看源碼,看文檔啊……對於Py3.5而言

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
};
/* Return pointers to the canonical representation cast to unsigned char,
Py_UCS2, or Py_UCS4 for direct character access.
No checks are performed, use PyUnicode_KIND() before to ensure
these will work correctly. */
#define PyUnicode_1BYTE_DATA(op) ((Py_UCS1*)PyUnicode_DATA(op))
#define PyUnicode_2BYTE_DATA(op) ((Py_UCS2*)PyUnicode_DATA(op))
#define PyUnicode_4BYTE_DATA(op) ((Py_UCS4*)PyUnicode_DATA(op))

這裡可以看到定義了個PyUnicode_Kind的玩意,用來區分存儲元。仔細找源碼樹來看的話,它分別對USC-1、USC-2、UCS-4三種不同的字符集分開存儲,並且各自有一個庫來處理這事。

當然,對老一點的Py3的話是一併扔wchar_t數組裡的,這就是OS和編譯器實現相關的事兒了。


在3.3之前(包括2.x中的unicode)都是以wchar_t數組的形式存在。當然編譯的時候可以選擇是2位元組還是4位元組。4位元組就可以存下所有的字元但內存開銷大,2位元組的話只能存bmp的,超出的部分用surrogate pairs表示,這就意味著代碼中需要額外的邏輯處理,內存減少了但邏輯更複雜,而且一些操作比如len,index都會有問題。(大概是這樣,老版本代碼沒仔細看過。)

從3.3開始內存就變成了「按需」分配了。不再是一刀切的2位元組或4位元組,而是看你的字元串中最大的字元需要幾個位元組去表示。對於ASCII和Latin1,就是一個1位元組的數組。如果有超出Latin1的,那麼就是2位元組數組,有超出bmp的就是4位元組數組。這樣就保證Python的string能存下所有Unicode字元,並且不需要surrogate pairs。這樣一個很大的好處就是可以節約很多內存,因為畢竟西方世界還是主流,很多代碼並不需要處理超出Latin1的字元。當然代碼中的一部分邏輯需要根據不同的類型進行不同的處理,還需要兼容之前的類型(C擴展會帶來以前布局的字元串)。鑒於代碼質量很高且高度優化過,這不是什麼問題。

所以說你的考慮是對的,的確浪費內存(但默認應該是2位元組,沒那麼嚴重。Linux發行版通常會發布4位元組版本),但你似乎忘了surrogate pairs這個東西。當然,3.3優化了實現,徹底解決了你的顧慮。


python3的str在內存中是以Unicode的方式存儲的。而Unicode最常用的是用兩個位元組表示一個字元,而非常偏僻的字元,就需要4個位元組。如果你寫的文本基本上全部是英文的話,用Unicode編碼比ASCII編碼需要多一倍的存儲空間,在存儲和傳輸上就十分不划算。

所以出現了把Unicode編碼轉化為「可變長編碼」的UTF-8編碼。UTF-8編碼把一個Unicode字元根據不同的數字大小編碼成1-6個位元組,常用的英文字母被編碼成1個位元組,漢字通常是3個位元組,只有很生僻的字元才會被編碼成4-6個位元組。

總結一下現在計算機系統通用的字元編碼工作方式:

在計算機內存中,統一使用Unicode編碼,當需要保存到硬碟或者需要傳輸的時候,就轉換為UTF-8編碼。


推薦閱讀:

自己寫的Python函數,如何調用?
OSX 10.9.4 如何安裝 Python 2.7?
python 3.4 新加入的asyncio是咋通過yield from實現非同步的?
如何看待 Instagram 將所有 Web 後端遷移到 Python 3.6 ?
Python混合類型計算中的一點疑惑?

TAG:Python | Unicode統一碼 | Python3x |