老Python總結的字典相關知識

雲崖先生 發表於 2021-05-03

字典

Python中的字典(dict)也被稱為對映(mapping)或者雜湊(hash),是支援Python底層實現的重要資料結構。

同時,也是應用最為廣泛的資料結構,內部採用hash儲存,儲存方式為鍵值對,需要注意的是鍵(key)必須為不可變型別,而值(value)可以是任意型別。

字典本身屬於可變容器型別,其中一組鍵值對被視為容器中的一組資料項。

字典的優點是單點查詢速度極快,而不能夠支援範圍查詢,此外也比較佔用記憶體。

基本宣告

以下是使用類的形式進行宣告:

userInfo = dict(name="YunYa", age=18, hobby=["football, music"])
print("值:%r,型別:%r" % (userInfo, type(userInfo)))

# 值:{'name': 'YunYa', 'age': 18, 'hobby': ['football, music']},型別:<class 'dict'>

也可以選擇使用更方便的字面量形式,使用{}對鍵值對進行包裹,鍵值對採用k:v的形式分割,多個鍵值對之間使用,進行分割:

userInfo = {"name": "YunYa", "age": 18, "hobby": ["football, music"]}
print("值:%r,型別:%r" % (userInfo, type(userInfo)))

# 值:{'name': 'YunYa', 'age': 18, 'hobby': ['football, music']},型別:<class 'dict'>

宣告dict時,千萬注意key只能是不可變型別。

如str,int,float,bool,tuple等等,使用可變型別作為key會丟擲異常。

宣告速度

字面量形式和例項類的形式宣告究竟哪個更快?

實際上字面量形式比例項類的速度大約快3倍。

我們可以使用一個timeit模組,來測出兩者的時間差:

$ python -m timeit -n 1000000 -r 5 -v "dict()"
raw times: 0.0865 0.0849 0.0845 0.0962 0.0842
1000000 loops, best of 5: 0.0842 usec per loop

$  python -m timeit -n 1000000 -r 5 -v "{}"
raw times: 0.0273 0.027 0.0278 0.0284 0.0265
1000000 loops, best of 5: 0.0265 usec per loop

① -n 語句執行多少次

② -r 重複計時器的次數,預設為5

為什麼會出現這樣的情況,可以使用dis模組來探索,該模組會通過反彙編來檢視到語句執行情況的位元組碼。

$ echo "{}" > demo.py
$ python -m dis demo.py
  1           0 BUILD_MAP                0
              3 POP_TOP
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE
              
$ echo "dict()" > demo.py
$ python -m dis demo.py
  1           0 LOAD_NAME                0 (dict)
              3 CALL_FUNCTION            0
              6 POP_TOP
              7 LOAD_CONST               0 (None)
             10 RETURN_VALUE

可以檢視到,使用dict()形式進行宣告時,必定會呼叫函式、呼叫函式的過程會發起系統呼叫棧的進出棧操作,故更加耗時。

不僅僅是字典的宣告、包括所有內建資料結構的宣告都推薦使用字面量形式。

如下所示,列表也有相同的情況發生,其他內建的資料結構不再進行演示:

$ echo "[]" > demo.py
$ python -m dis demo.py
  1           0 BUILD_LIST               0
              3 POP_TOP
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE
              
$ echo "list()" > demo.py
$ python -m dis demo.py
  1           0 LOAD_NAME                0 (list)
              3 CALL_FUNCTION            0
              6 POP_TOP
              7 LOAD_CONST               0 (None)
             10 RETURN_VALUE

續行操作

在Python中,字典中的資料項如果過多,可能會導致整個字典太長。

Python雖然提供了續行符\,但是在字典中可以忽略續行符,如下所示:

userInfo = {
    "name": "YunYa",
    "age": 18,
    "hobby": ["football, music"]}
print("值:%r,型別:%r" % (userInfo, type(userInfo)))

# 值:{'name': 'YunYa', 'age': 18, 'hobby': ['football, music']},型別:<class 'dict'>

多維巢狀

字典中可以進行多維巢狀,如字典套字典,字典套元組,字典套列表等:

dic = {
    "k1": [1, 2, 3],
    "k2": (1, 2, 3),
    "k3": {
        "k3-1": 1,
        "k3-2": 2,
    },
}

型別轉換

字典可以與布林型別和字串進行轉換,這是最常用的。

dic = {"k1": "v1", "k2": "v2"}
boolDict = bool(dic)  # 布林型別
strDict = str(dic)    # 字串型別

print("值:%r,型別:%r" % (boolDict, type(boolDict)))
print("值:%r,型別:%r" % (strDict, type(strDict)))

# 值:True,型別:<class 'bool'>
# 值:"{'k1': 'v1', 'k2': 'v2'}",型別:<class 'str'>

如果要將字典轉換為列表、元組、集合型別,直接轉換隻會拿到鍵,並不會拿到值。

尤其注意這一點,但是其實這樣用的場景十分少見,記住就行了:

dic = {"k1": "v1", "k2": "v2"}
listDict = list(dic)    # 列表型別
tupleDict = tuple(dic)  # 元組型別
setDict = set(dic)      # 集合型別

print("值:%r,型別:%r" % (listDict, type(listDict)))
print("值:%r,型別:%r" % (tupleDict, type(tupleDict)))
print("值:%r,型別:%r" % (setDict, type(setDict)))

# 值:['k1', 'k2'],型別:<class 'list'>
# 值:('k1', 'k2'),型別:<class 'tuple'>
# 值:{'k2', 'k1'},型別:<class 'set'>

重複key

一個字典中的key必須是唯一的,若不是唯一的則value可能面臨被覆蓋的危險:

dic = {"name": "雲崖", "age": 18, "name": "Yunya"}
print(dic)

# {'name': 'Yunya', 'age': 18}

同理,True和1,False和0也會彼此進行覆蓋:

dic = {True: "雲崖", "age": 18, 1: "Yunya"}
print(dic)

# {True: 'Yunya', 'age': 18}

如果你對此不瞭解,建議回退到布林型別一章節中再次檢視True&1andFalse&0之間的關係。

[]操縱字典

由於字典並非順序儲存(下面會簡單介紹),故不支援索引操作。

但是字典也提供了[]操作語法,它是根據key來操作value的。

增刪改查

以下示例展示瞭如何使用[]對字典中的value進行操縱:

dic = {"k1": "v1", "k2": "v2"}

# 增
dic["k3"] = "v3"
print(dic)
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}

# 刪,如果沒有該key,則丟擲keyError
del dic["k2"]
print(dic)
# {'k1': 'v1', 'k3': 'v3'}

# 改,如果沒有該key,則丟擲keyError
dic["k3"] = "VV3"
print(dic)
# {'k1': 'v1', 'k3': 'VV3'}

# 查,如果沒有該key,則丟擲keyError
result = dic["k1"]
print(result)
# v1

多維操作

字典套列表的多維操作如下,首先需要拿到該列表:

dic = {"k1": [1, 2, 3, 4]}

# 取出3
result = dic["k1"][2]
print(result)
# 3

# k1的列表,新增元素 "A"
dic["k1"].append("A")
print(dic)
# {'k1': [1, 2, 3, 4, 'A']}

字典套字典的多維操作如下,首先需要拿到被操縱的字典:

dic = {
    "k1":{
        "k1-1":{
            "k1-2":{
                "k1-3":"HELLO,WORLD",
            }
        }
    }
}

# 拿到 k1-3 對應的value
result = dic["k1"]["k1-1"]["k1-2"]["k1-3"]
print(result)
# HELLO,WORLD

解構語法

**語法

**語法用於將字典中的k:v全部提取出來。

我們可以利用該語法的特性來對字典進行合併,將兩個舊字典合併成一個新字典:

dic_1 = {"d1k1": "A", "d1k2": "B"}
dic_2 = {"d2k1": "C", "d2k2": "D"}
result = {**dic_1, **dic_2}
print(result)
# {'d1k1': 'A', 'd1k2': 'B', 'd2k1': 'C', 'd2k2': 'D'}

解構賦值

字典支援平行變數賦值操作嗎?當然可以!但是這樣只會拿到字典的key:

dic = {"k1": "v1", "k2": "v2"}

first, last = dic
print(first)
print(last)

# k1
# k2

有辦法拿到value麼?藉助字典的values()方法即可做到,它的本質是將value全部提取出來,組成一個可迭代物件:

dic = {"k1": "v1", "k2": "v2"}

first, last = dic.values()
print(first)
print(last)

# v1
# v2

你可以理解為,將value全部提取出來後轉換為一個列表,類似於[“v1”, “v2”],在Python2中的確是這樣,但是到了Python3中做法改變了,目前按下不表。

對於一些不想要的資料項,你也可以按照列表的解構賦值操作來進行,這裡不再舉例。

常用方法

方法一覽

常用的dict方法一覽:

方法名 返回值 描述
get() v or None 取字典key對應的value,如果key不存在返回None
setdefault() v 獲取字典key對應的value,如該字典中不存在被獲取的key則會進行新增k:v,並返回v
update() None 對原有的字典進行更新
pop() v 刪除該字典中的鍵值對,如果不填入引數key或者key不存在則丟擲異常
keys() Iterable 返回一個可迭代物件,該可迭代物件中只存有字典的所有key
values() Iterable 返回一個可迭代物件,該可迭代物件中只存有字典的所有value
items() Iterable 返回一個可迭代物件,該可迭代物件中存有字典中所有的key與value,類似於列表套元組
clear() None 清空當前字典

基礎公用函式:

函式名 返回值 描述
len() integer 返回容器中的專案數

獲取長度

使用len()方法來進行字典長度的獲取。

返回int型別的值。

dic = {"name": "雲崖", "age": 18}
print(len(dic))

# 2 一組鍵值對被視為一個資料項,故2組鍵值對長度為2

Python在對內建的資料型別使用len()方法時,實際上是會直接的從PyVarObject結構體中獲取ob_size屬性,這是一種非常高效的策略。

PyVarObject是表示記憶體中長度可變的內建物件的C語言結構體。

直接讀取這個值比呼叫一個方法要快很多。

get()

使用get()方法獲取字典key對應的value,相比於[]操作更加的人性化,因為[]一旦獲取不存在的key則會丟擲異常,而該方法則是返回None。

dic = {"name": "雲崖", "age": 18}
username = dic.get("name")
userhobby = dic.get("hobby")

print("使用者姓名:",username)
print("使用者愛好:",userhobby)

# 使用者姓名: 雲崖
# 使用者愛好: None

setdefault()

使用setdefault()方法來獲取字典key對應的value,如該字典中不存在被獲取的key則會進行新增k:v,並返回v。

返回字典原有的value或者新設定的k:v。

dic = {"name": "雲崖", "age": 18}

# 字典有name,則取字典裡的name
username = dic.setdefault("name","雲崖先生")   

# 字典沒有hobby,則設定hobby的value為足球與籃球並返回
userhobby = dic.setdefault("hobby","足球與籃球")  

print("使用者姓名:",username)
print("使用者愛好:",userhobby)

# 使用者姓名: 雲崖
# 使用者愛好: 足球與籃球

update()

使用update()方法對原有的字典進行更新。

返回None。

dic = {"name": "雲崖", "age": 18}

dic.update(
    {"hobby": ["籃球", "足球"]}
)

print(dic)

# {'name': '雲崖', 'age': 18, 'hobby': ['籃球', '足球']}

pop()

使用pop()方法刪除該字典中的鍵值對,如果不填入引數key或者key不存在則丟擲異常。

返回被刪除的value。

dic = {"name": "雲崖", "age": 18}

result = dic.pop("age")

print(result)
print(dic)

# 18
# {'name': '雲崖'}

keys()

返回一個可迭代物件,該可迭代物件中只存有字典的所有key。

Python2中返回的是列表,Python3中返回的是可迭代物件。

dic = {"name": "雲崖", "age": 18}

key_iter = dic.keys()

print(key_iter)

# dict_keys(['name', 'age'])

values()

返回一個可迭代物件,該可迭代物件中只存有字典的所有value。

Python2中返回的是列表,Python3中返回的是可迭代物件。

dic = {"name": "雲崖", "age": 18}

value_iter = dic.values()

print(value_iter)

# dict_values(['雲崖', 18])

items()

返回一個可迭代物件,該可迭代物件中存有字典中所有的key與value,類似於列表套元組。

Python2中返回的是二維列表,Python3中返回的是可迭代物件。

dic = {"name": "雲崖", "age": 18}

items_iter = dic.items()

print(items_iter)

# dict_items([('name', '雲崖'), ('age', 18)])

clear()

清空當前字典。

返回None。

dic = {"name": "雲崖", "age": 18}

dic.clear()

print(dic)

# {}

其他方法

方法 返回值 描述
popitem() (k, v) 隨機刪除一組鍵值對,並將刪除的鍵值放到元組內返回
fromkeys(iter,value) dict 第一個引數是可迭代物件,其中每一個元素都為新生成字典的key,第二個引數為同一的value值

示例演示:

dic1 = dict(k1="v1", k2="v2", k3="v3", k4="v4")
print(dic1.popitem())  
# ('k4', 'v4') 

dic2 = dict.fromkeys([1, 2, 3, 4], None)
print(dic2)  
# {1: None, 2: None, 3: None, 4: None}

原理淺析

高效查詢

為什麼要有字典這種資料結構?

如果對一個無序的列表查詢其中某一個value(不能進行排序),必須經過一個一個的遍歷,速度會很慢。

[3, 2, 8, 9, 11, 13]

# 如果要獲取資料項11,必須經過5次查詢

有沒有一種辦法,能夠讓速度加快?

為了不違背不能排序的前提,我們只能在列表存入value的時候做文章。

我們可以每個value都造一個獨一無二的身份標識,根據這個身份識別符號計算出value需要插入到列表的索引位置。

在取的時候同理,通過身份識別符號直接就可以拿到value所在列表的索引值,無疑速度會快很多。

一個小總結:

  • 有一個身份標識,身份標識必須是唯一的
  • 提供一個根據身份標識計算出插入位置的演算法

回到字典的本質,字典的key就是value的身份標識,而根據key計算出插入位置的演算法被封裝在了hash()函式中,這個演算法也被稱之為hash演算法。

為什麼key必須是唯一的,參照下面這個示例:

["k1", "k2", "k3", "k4", "k5", "k6"]
[  3,    2,    8,    9,   11,   13]
  • 假如k5變成了k6,那麼就有2個k6對應2個不同的value
  • 這麼做的後果就是,使用k6獲取value的時候,根本不知道你需要的value是哪一個

所以,乾脆Python規定,key必須是不可變型別!如果有重複則新的覆蓋舊的。

hash()過程

如何通過hash()函式,確定value的插入位置?

實際上每個鍵值對在存入字典之前,都會通過hash()函式對key計算出一個hash值(也被稱為雜湊值):

>>> hash("k1")
7036545863130266253

而字典的底層結構是由一個2維陣列巢狀組成的,也被稱為雜湊表、hash表。

如下所示,每次建立字典的時候,字典都會初始化生成一個固定長度且內容全是空的2維陣列,Python內部生成的雜湊表長度為8(可參見PyDictObject結構體原始碼):

[
	 ①  ②  ③
	[空, 空, 空], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	[空, 空, 空], index: 3
	[空, 空, 空], index: 4
	[空, 空, 空], index: 5
	[空, 空, 空], index: 6
	[空, 空, 空]  index: 7
]

①:存放根據key計算出的hash值

②:存放key的引用

③:存放value的引用

現在,我們要儲存name:yunya的鍵值對,對name計算hash值:

>>> hash("name")
3181345887314224636

用計算出的hash值與雜湊表長度進行求餘運算:

>>> 3181345887314224636 % 8
4

得到結果是4,就在雜湊表4的索引位置插入:

[
	 ①  ②  ③
	[空, 空, 空], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	[空, 空, 空], index: 3
	[3181345887314224636, "name"的引用, "yunya"], index: 4
	[空, 空, 空], index: 5
	[空, 空, 空], index: 6
	[空, 空, 空]  index: 7
]

再次插入age:18,並用計算出的hash值與雜湊表長度進行求餘運算:

>>> hash("age")
7064862892218627464
>>> 7064862892218627464 % 8
0

得到的結果是0,就在雜湊表0的索引位置插入:

[
	 ①  ②  ③
	[7064862892218627464, "age"的引用, 18], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	[空, 空, 空], index: 3
	[3181345887314224636, "name"的引用, "yunya"], index: 4
	[空, 空, 空], index: 5
	[空, 空, 空], index: 6
	[空, 空, 空]  index: 7
]

可以看見,這個2維陣列不是按照順序進行插入的,總有一些空的位置存在,該陣列也被稱為稀鬆陣列。

由於陣列是稀鬆的,所以dict不支援範圍獲取(能獲取到空值),但單點存取的速度很快。

讀取的時候也同理,但是Python的hash函式底層實現是否真的利用hash值對稀鬆陣列長度進行簡單的求餘運算,這個還有待商榷。

因為hash演算法的實現有很多種,長度求餘隻是最為簡單的一種而已,這裡用作舉例,如果想具體瞭解其演算法可以檢視Python原始碼,PyDictObject.c中的perturb。

雜湊衝突

現在,我們的這個雜湊表中0和4的索引位置都已經存在資料了。

如果現在存入一個teacher:wang,那麼結果會是怎麼樣?

>>> hash("teacher")
4789346189807557228
>>> 4789346189807557228 % 8
4

可以發現,teacher的hash值求餘算結果也是4,這個時候就會發生雜湊衝突。

最常見的做法是,向後挪!因為索引5的位置是空的,我們可以將這個鍵值對插入到索引5的位置:

[
	 ①  ②  ③
	[7064862892218627464, "age"的引用, 18], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	[空, 空, 空], index: 3
	[3181345887314224636, "name"的引用, "yunya"], index: 4
	[4789346189807557228, "teacher"的引用, "wang"], index: 5
	[空, 空, 空], index: 6
	[空, 空, 空]  index: 7
]

這種查詢空位的方法叫做開放定址法(openaddressing),向後查詢也被稱為線性探測(linearprobing)。

如果此時又插入一個資料項,最後key的插入索引位置也是4,則繼續向後查詢空位,如果查詢到7還是沒有空位,又從0開始找。

上述方法是解決雜湊衝突的基礎方案,當然也還有更多的其他解決方案,這裡再說就過頭了,放在後面數構一章中再進行介紹吧。

擴容機制

Python的dict會對雜湊表的容量做出判定。

當容量超過三分之二時,即進行擴容(resize)機制。

如果雜湊表大小為8,在即將插入第3個鍵值對時進行擴容,擴容策略為已有雜湊表鍵值對個數 * 2。

即雜湊表大小擴充套件為12。

如果整個雜湊表已有鍵值對個數達到了50000,則擴容策略為已有雜湊表鍵值對個數 * 4。

此外,dict只會進行擴容,不會進行縮容,如果刪除了1個鍵值對,其記憶體空間佔用的位置並不會釋放。

不同的key優化策略

整形是其本身

整形的hash值是其本身:

>>> hash(1)
1
>>> hash(2)
2
>>> hash(3)
3
>>> hash(10000)
10000

加鹽策略

在Python3.3開始,str、bytes、datetime等物件在計算雜湊值的時候會進行加鹽處理。

這個鹽引用內部的一個常量,該常量在每次CPython啟動時會生成不同的鹽值。

所以你會發現每次重啟Python3.3以後的直譯器,對相同字串進行hash()求雜湊值得出的結果總是不一樣的:

$ python3
Python 3.6.8 (v3.6.8:3c6b436a57, Dec 24 2018, 02:04:31)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("k1")
8214688532022610754
>>> exit()

$ python3
Python 3.6.8 (v3.6.8:3c6b436a57, Dec 24 2018, 02:04:31)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("k1")
-7444020267993088839
>>> exit()

再看Python2.7,由於沒有加鹽策略,所以每次重啟得到的hash結果是相同的:

$ python
Python 2.7.10 (default, Feb 22 2019, 21:55:15)
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("k1")
13696082283123634
>>> exit()

$ python
Python 2.7.10 (default, Feb 22 2019, 21:55:15)
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("k1")
13696082283123634
>>> exit()

有序字典

字典無序的觀念似乎已經深入人心,但那已經都是過去式了。

在Python3.6之後,字典變的有序了。

2012年12月10日星期一的時候,R. David Murray向Python官方傳送了一封郵件,提出建議讓Python的字典變的有序。

這樣的做法能夠讓Python字典的空間佔用量更小,迭代速度更快,以下是郵件內容:

https://mail.python.org/pipermail/python-dev/2012-December/123028.html

我們先看看2.7中的字典:

>>> {chr(i) : i for i in range(10)}
{'\x01': 1, '\x00': 0, '\x03': 3, '\x02': 2, '\x05': 5, '\x04': 4, '\x07': 7, '\x06': 6, '\t': 9, '\x08': 8}

再來看3.6中的字典:

>>> {chr(i) : i for i in range(10)}
{'\x00': 0, '\x01': 1, '\x02': 2, '\x03': 3, '\x04': 4, '\x05': 5, '\x06': 6, '\x07': 7, '\x08': 8, '\t': 9}

果然!它確實變的有序了,關於具體細節,可以參照這封郵件,已經表述的很清楚了,下面做一個簡單的示例。

首先,以前的雜湊表就是一個單純的稀鬆二維陣列:

[
	[空, 空, 空], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	...
]

鍵值對的讀取順序來源與填加順序。

索引靠前的會被先遍歷拿到,索引靠後只能後被遍歷出來。

如果這個雜湊表長度為8,前7個都沒有資料項存入,僅有8才有,那麼遍歷完整個雜湊表需要8次:

[
	[空, 空, 空], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	...
	[hash值, key的引用, value的引用], index: 7
]

而Python3.6之後,又新增了一個順序陣列,該陣列與雜湊表的長度相等,初始均為8,並且會跟隨雜湊表的擴容而進行擴容,如下示例初始狀態:

[None, None, None, ...]

[
	[空, 空, 空], index: 0
	[空, 空, 空], index: 1
	[空, 空, 空], index: 2
	...
]

如果說第1個鍵值對,被插入到雜湊表索引1的位置,那麼在順序陣列中,則在索引0處記錄下該鍵值對被插入在雜湊表中的位置(1),如下圖所示:

[1, None, None, ...]

[
	[空, 空, 空], index: 0
	[hash值, key的引用, value的引用], index: 1
	[空, 空, 空], index: 2
	...
]

如果第2個鍵值對,被插入到雜湊表索引0的位置,那麼在順序陣列中,則在索引1處記錄下該鍵值對被插入在雜湊表中的位置(0),如下圖所示:

[1, 0, None, ...]

[
	[hash值, key的引用, value的引用], index: 0
	[hash值, key的引用, value的引用], index: 1
	[空, 空, 空], index: 2
	...
]

在遍歷的時候,會遍歷這個順序陣列,然後通過索引值拿到雜湊表中對應位置的資料項,如果遍歷到的值為None就結束遍歷,而不用遍歷完整個雜湊表:

[1, 0, 7, None, None, None, None, None]

[
	[hash值, key的引用, value的引用], index: 0
	[hash值, key的引用, value的引用], index: 1
	[空, 空, 空], index: 2
	...
	[hash值, key的引用, value的引用], index: 7
]

類似於:

hashTableOrderArray = [1, 0, 7, None, None, None, None, None]
hashTable = [
    ["hash", "k2", "v2"],
    ["hash", "k1", "v1"],
    [None, None, None],
    [None, None, None],
    [None, None, None],
    [None, None, None],
    [None, None, None],
    ["hash", "k3", "v3"],
]

n = 0

while n < len(hashTable):
    if hashTableOrderArray[n] is not None:
        print(hashTable[hashTableOrderArray[n]])
    else:
        break
    n += 1

這樣只需遍歷3次即可,而如果不用這個順序陣列,則要完整遍歷整個雜湊表,即8次才能拿出所有的鍵值對。

字典特性

字典特性如下:

  • 字典是一個可變的容器型別
  • 字典內部由雜湊表組成
  • 字典的單點讀寫速度很快,但是不支援範圍查詢
  • 字典的key必須是不可變的
  • 字典在3.6之後變得有序了,這樣做提升了遍歷效率

參考文章:PyDictObject實現