python中幾個常見的“黑盒子”之 列表list


python常見的數據類型有:字符串,布爾類型,整數,浮點數,數字,日期,列表,元祖,字典。相信前面6個大家都非常的熟悉,但是對於python的列表,元祖,字典我有時候一直在想其內部的實現是怎么樣子的,它們就像一個“黑盒子”一樣,下面記錄一下對於“列表 list”理解過程:

其實,在最開始我一直以為python的列表是通過鏈表實現的,直到一天,應該說是誤打誤撞,當我通過交互模式創建一個列表的時候,然后通過id()函數打印出列表中每個元素的地址時,我發現它們的地址是連續的,然后推測python的列表list不是我們傳統意義上面的列表(雖然這個推測應該算是誤打誤撞吧,真實的原因不是這個,這個每個元素地址連續也是因為正好湊巧而已,但是卻引起了我的進一步探索和思考),也就是說不是通過鏈表實現的(因為如果是鏈表的話其元素地址肯定是不連續的),然后我就猜測這種內存地址連續的情況,python的列表應該是通過數組的形式進行存儲實現的。

通過查看python文檔:https://wiki.python.org/moin/TimeComplexity,其中記錄了這么一句話:Internally, a list is represented as an array;這也進一步驗證了我推測的結果。然后查看python的源碼:

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

其中記錄了這么一個結構體(省略了源碼中的大部分注釋,有興趣可以找到源碼看一下),其中:

  ob_item是指向列表對象的指針數組;

  allocated是申請內存的槽的個數。

接着,通過源碼獲悉,python列表的存儲形式就是以數組array的形式進行存儲的,然后數組中每一個元素其實存儲的是列表對象的指針。

 

現在我們搞明白了python列表的內部實現方式,所以進一步思考了如下問題:

1.對於列表操作,append操作肯定比insert操作效率要高:

  因為,對於數組來說,用append操作從后面追加一個元素,時間復雜度來說是常量級的,但是用insert操作進行數組的插入來說,當插入第一位或者中間某一位的時候,該元素后面的所有元素都要相應的往后面挪動一位。

2.python中列表的存儲形式是數組的形式,這也突出了其和鏈表的很大的區別:

  a.當我們按照給定的索引值進行某一個元素的訪問的話,數組的效率肯定是比鏈表的效率高出不少的。

     因為,對於數組來說,我們可以直接計算出目標元素在內存中的具體位置,然后直接對其訪問;但是對於鏈表來說,我們要做的是去遍歷整個鏈表才可以得到目標元素。

  b.但是對於插入insert操作來說,情況就和上面不同了。

     因為對於鏈表來說,我們只要知道在哪里執行insert插入操作就可以了,無論該列表中含有多少元素,操作時間都基本是相同的,造作的成本非常低;但是對於數組就不同了,每次執行插入操作的時候,都需要移動插入點右邊的所有元素。甚至有時候,因為插入的元素過多,開始分配的內存空間不夠,還需要把這個列表元素整體搬到一個更大的數組中(這是我開始的猜測)。當然,對於這種情況,python的開發者早就想好了對應的方法,就是append操作通常會采用一種動態數組或向量的特定解決方案:

       將內存分配得過大一些,並且等到其要溢出時,在線性時間內再次重新分配內存。

  但是,這樣似乎還是我上面猜測的那樣,不是會讓append和insert一樣糟糕么,其實不是這樣子的,因為就算這倆種情況都有可能去搬動大量的元素,但最主要的不同是:對於append操作,發生的可能性要小非常多,事實上,我們能夠確保每次搬入的數組都大於原數組一定的比例,那么該操作的平均成本(每次搬動的開銷平均分攤到每次append操作中去),這樣的時間復雜度通常是常量級別的。這里也從而得出來,python的列表對內存的開銷是比較大的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM