日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

為什么Python 3.6以后字典有序并且效率更高?

系統(tǒng) 2106 0

在Python 3.5(含)以前,字典是不能保證順序的,鍵值對(duì)A先插入字典,鍵值對(duì)B后插入字典,但是當(dāng)你打印字典的Keys列表時(shí),你會(huì)發(fā)現(xiàn)B可能在A的前面。

但是從Python 3.6開始,字典是變成有順序的了。你先插入鍵值對(duì)A,后插入鍵值對(duì)B,那么當(dāng)你打印Keys列表的時(shí)候,你就會(huì)發(fā)現(xiàn)B在A的后面。

不僅如此,從Python 3.6開始,下面的三種遍歷操作,效率要高于Python 3.5之前:

          for key in 字典
          

for value in 字典.values()

for key, value in 字典.items()

從Python 3.6開始,字典占用內(nèi)存空間的大小,視字典里面鍵值對(duì)的個(gè)數(shù),只有原來的30%~95%。

Python 3.6到底對(duì)字典做了什么優(yōu)化呢?為了說明這個(gè)問題,我們需要先來說一說,在Python 3.5(含)之前,字典的底層原理。

當(dāng)我們初始化一個(gè)空字典的時(shí)候,CPython的底層會(huì)初始化一個(gè)二維數(shù)組,這個(gè)數(shù)組有8行,3列,如下面的示意圖所示:

          my_dict = {}
          

'''
此時(shí)的內(nèi)存示意圖
[[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---]]
'''

現(xiàn)在,我們往字典里面添加一個(gè)數(shù)據(jù):

          my_dict['name'] = 'kingname'
          

'''
此時(shí)的內(nèi)存示意圖
[[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[1278649844881305901, 指向name的指針, 指向kingname的指針],
[---, ---, ---],
[---, ---, ---]]
'''

這里解釋一下,為什么添加了一個(gè)鍵值對(duì)以后,內(nèi)存變成了這個(gè)樣子:

首先我們調(diào)用Python 的 hash 函數(shù),計(jì)算 name 這個(gè)字符串在 當(dāng)前運(yùn)行時(shí) 的hash值:

          >>> hash('name')
          
1278649844881305901

特別注意,我這里強(qiáng)調(diào)了『當(dāng)前運(yùn)行時(shí)』,這是因?yàn)椋琍ython自帶的這個(gè) hash 函數(shù),和我們傳統(tǒng)上認(rèn)為的Hash函數(shù)是不一樣的。Python自帶的這個(gè) hash 函數(shù)計(jì)算出來的值,只能保證在每一個(gè)運(yùn)行時(shí)的時(shí)候不變,但是當(dāng)你關(guān)閉Python再重新打開,那么它的值就可能會(huì)改變,如下圖所示:

image

假設(shè)在某一個(gè)運(yùn)行時(shí)里面, hash('name') 的值為 1278649844881305901 。現(xiàn)在我們要把這個(gè)數(shù)對(duì)8取余數(shù):

          >>> 1278649844881305901 % 8
          
5

余數(shù)為5,那么就把它放在剛剛初始化的二維數(shù)組中,下標(biāo)為5的這一行。由于 name kingname 是兩個(gè)字符串,所以底層C語言會(huì)使用兩個(gè)字符串變量存放這兩個(gè)值,然后得到他們對(duì)應(yīng)的指針。于是,我們這個(gè)二維數(shù)組下標(biāo)為5的這一行,第一個(gè)值為 name 的hash值,第二個(gè)值為 name 這個(gè)字符串所在的內(nèi)存的地址(指針就是內(nèi)存地址),第三個(gè)值為 kingname 這個(gè)字符串所在的內(nèi)存的地址。

現(xiàn)在,我們?cè)賮聿迦雰蓚€(gè)鍵值對(duì):

          my_dict['age'] = 26
          
my_dict['salary'] = 999999

'''
此時(shí)的內(nèi)存示意圖
[[-4234469173262486640, 指向salary的指針, 指向999999的指針],
[1545085610920597121, 執(zhí)行age的指針, 指向26的指針],
[---, ---, ---],
[---, ---, ---],
[---, ---, ---],
[1278649844881305901, 指向name的指針, 指向kingname的指針],
[---, ---, ---],
[---, ---, ---]]
'''

那么字典怎么讀取數(shù)據(jù)呢?首先假設(shè)我們要讀取 age 對(duì)應(yīng)的值。

此時(shí),Python先計(jì)算在當(dāng)前運(yùn)行時(shí)下面, age 對(duì)應(yīng)的Hash值是多少:

          >>> hash('age')
          
1545085610920597121

現(xiàn)在這個(gè)hash值對(duì)8取余數(shù):

          >>> 1545085610920597121 % 8
          
1

余數(shù)為1,那么二維數(shù)組里面,下標(biāo)為1的這一行就是需要的鍵值對(duì)。直接返回這一行第三個(gè)指針對(duì)應(yīng)的內(nèi)存中的值,就是 age 對(duì)應(yīng)的值 26

當(dāng)你要循環(huán)遍歷字典的Key的時(shí)候,Python底層會(huì)遍歷這個(gè)二維數(shù)組,如果當(dāng)前行有數(shù)據(jù),那么就返回Key指針對(duì)應(yīng)的內(nèi)存里面的值。如果當(dāng)前行沒有數(shù)據(jù),那么就跳過。所以總是會(huì)遍歷整個(gè)二位數(shù)組的每一行。

每一行有三列,每一列占用8byte的內(nèi)存空間,所以每一行會(huì)占用24byte的內(nèi)存空間。

由于Hash值取余數(shù)以后,余數(shù)可大可小,所以字典的Key并不是按照插入的順序存放的。

注意,這里我省略了與本文沒有太大關(guān)系的兩個(gè)點(diǎn):

          開放尋址
          

在Python 3.6以后,字典的底層數(shù)據(jù)結(jié)構(gòu)發(fā)生了變化,現(xiàn)在當(dāng)你初始化一個(gè)空的字典以后,它在底層是這樣的:

          my_dict = {}
          

'''
此時(shí)的內(nèi)存示意圖
indices = [None, None, None, None, None, None, None, None]

entries = []
'''

當(dāng)你初始化一個(gè)字典以后,Python單獨(dú)生成了一個(gè)長(zhǎng)度為8的一維數(shù)組。然后又生成了一個(gè)空的二維數(shù)組。

現(xiàn)在,我們往字典里面添加一個(gè)鍵值對(duì):

          my_dict['name'] = 'kingname'
          

'''
此時(shí)的內(nèi)存示意圖
indices = [None, 0, None, None, None, None, None, None]

entries = [[-5954193068542476671, 指向name的指針, 執(zhí)行kingname的指針]]
'''

為什么內(nèi)存會(huì)變成這個(gè)樣子呢?我們來一步一步地看:

在當(dāng)前運(yùn)行時(shí), name 這個(gè)字符串的hash值為 -5954193068542476671 ,這個(gè)值對(duì)8取余數(shù)是1:

          >>> hash('name')
          
-5954193068542476671

hash('name') % 8
1

所以,我們把 indices 這個(gè)一維數(shù)組里面,下標(biāo)為1的位置修改為0。

這里的0是什么意思呢?0是二位數(shù)組 entries 的索引。現(xiàn)在 entries 里面只有一行,就是我們剛剛添加的這個(gè)鍵值對(duì)的三個(gè)數(shù)據(jù): name 的hash值、指向 name 的指針和指向 kinganme 的指針。所以 indices 里面填寫的數(shù)字0,就是剛剛我們插入的這個(gè)鍵值對(duì)的數(shù)據(jù)在二位數(shù)組里面的行索引。

好,現(xiàn)在我們?cè)賮聿迦雰蓷l數(shù)據(jù):

            my_dict['address'] = 'xxx'
            
my_dict['salary'] = 999999

'''
此時(shí)的內(nèi)存示意圖
indices = [1, 0, None, None, None, None, 2, None]

entries = [[-5954193068542476671, 指向name的指針, 執(zhí)行kingname的指針],
[9043074951938101872, 指向address的指針,指向xxx的指針],
[7324055671294268046, 指向salary的指針, 指向999999的指針]
]
'''

現(xiàn)在如果我要讀取數(shù)據(jù)怎么辦呢?假如我要讀取 salary 的值,那么首先計(jì)算 salary 的hash值,以及這個(gè)值對(duì)8的余數(shù):

            >>> hash('salary')
            
7324055671294268046

hash('salary') % 8
6

那么我就去讀 indices 下標(biāo)為6的這個(gè)值。這個(gè)值為2.

然后再去讀entries里面,下標(biāo)為2的這一行的數(shù)據(jù),也就是salary對(duì)應(yīng)的數(shù)據(jù)了。

新的這種方式,當(dāng)我要插入新的數(shù)據(jù)的時(shí)候,始終只是往 entries 的后面添加數(shù)據(jù),這樣就能保證插入的順序。當(dāng)我們要遍歷字典的Keys和Values的時(shí)候,直接遍歷 entries 即可,里面每一行都是有用的數(shù)據(jù),不存在跳過的情況,減少了遍歷的個(gè)數(shù)。

最后在此推薦小編創(chuàng)建的Python學(xué)習(xí)交流群:835017344,這里是python學(xué)習(xí)者聚集地,有大牛答疑,有資源共享!有想學(xué)習(xí)python編程的,或是轉(zhuǎn)行,或是大學(xué)生,還有工作中想提升自己能力的,正在學(xué)習(xí)的小伙伴歡迎加入學(xué)習(xí)。

老的方式,當(dāng)二維數(shù)組有8行的時(shí)候,即使有效數(shù)據(jù)只有3行,但它占用的內(nèi)存空間還是 8 * 24 = 192 byte。但使用新的方式,如果只有三行有效數(shù)據(jù),那么 entries 也就只有3行,占用的空間為3 * 24 =72 byte,而 indices 由于只是一個(gè)一維的數(shù)組,只占用8 byte,所以一共占用 80 byte。內(nèi)存占用只有原來的41%。


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 绩溪县| 巴彦县| 正安县| 南乐县| 周宁县| 田东县| 玉林市| 内黄县| 铜山县| 西城区| 陆良县| 浮梁县| 中西区| 济源市| 佳木斯市| 温泉县| 普兰县| 滁州市| 从江县| 福清市| 九江市| 琼海市| 宝坻区| 柳河县| 颍上县| 石台县| 嘉荫县| 南汇区| 广水市| 沙洋县| 林口县| 介休市| 特克斯县| 阿拉善左旗| 绥滨县| 罗田县| 仁布县| 杨浦区| 孝昌县| 和硕县| 获嘉县|