字典

Python中的字典(dict)也被称为映射(mapping)或者散列(hash),是支持Python底层实现的重要数据结构。

同时,也是应用最为广泛的数据结构,内部采用hash存储,存储方式为键值对,需要注意的是键(key)必须为不可变类型,而值(value)可以是任意类型。

字典本身属于可变容器类型,其中一组键值对被视为容器中的一组数据项。

字典的优点是单点查找速度极快,而不能够支持范围查找,此外也比较占用内存。

基本声明

以下是使用类的形式进行声明:

  1. userInfo = dict(name="YunYa", age=18, hobby=["football, music"])
  2. print("值:%r,类型:%r" % (userInfo, type(userInfo)))
  3. # 值:{'name': 'YunYa', 'age': 18, 'hobby': ['football, music']},类型:<class 'dict'>

也可以选择使用更方便的字面量形式,使用{}对键值对进行包裹,键值对采用k:v的形式分割,多个键值对之间使用,进行分割:

  1. userInfo = {"name": "YunYa", "age": 18, "hobby": ["football, music"]}
  2. print("值:%r,类型:%r" % (userInfo, type(userInfo)))
  3. # 值:{'name': 'YunYa', 'age': 18, 'hobby': ['football, music']},类型:<class 'dict'>

声明dict时,千万注意key只能是不可变类型。

如str,int,float,bool,tuple等等,使用可变类型作为key会抛出异常。

声明速度

字面量形式和实例类的形式声明究竟哪个更快?

实际上字面量形式比实例类的速度大约快3倍。

我们可以使用一个timeit模块,来测出两者的时间差:

  1. $ python -m timeit -n 1000000 -r 5 -v "dict()"
  2. raw times: 0.0865 0.0849 0.0845 0.0962 0.0842
  3. 1000000 loops, best of 5: 0.0842 usec per loop
  4. $ python -m timeit -n 1000000 -r 5 -v "{}"
  5. raw times: 0.0273 0.027 0.0278 0.0284 0.0265
  6. 1000000 loops, best of 5: 0.0265 usec per loop

① -n 语句执行多少次

② -r 重复计时器的次数,默认为5

为什么会出现这样的情况,可以使用dis模块来探索,该模块会通过反汇编来查看到语句执行情况的字节码。

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

可以查看到,使用dict()形式进行声明时,必定会调用函数、调用函数的过程会发起系统调用栈的进出栈操作,故更加耗时。

不仅仅是字典的声明、包括所有内置数据结构的声明都推荐使用字面量形式。

如下所示,列表也有相同的情况发生,其他内置的数据结构不再进行演示:

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

续行操作

在Python中,字典中的数据项如果过多,可能会导致整个字典太长。

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

  1. userInfo = {
  2. "name": "YunYa",
  3. "age": 18,
  4. "hobby": ["football, music"]}
  5. print("值:%r,类型:%r" % (userInfo, type(userInfo)))
  6. # 值:{'name': 'YunYa', 'age': 18, 'hobby': ['football, music']},类型:<class 'dict'>

多维嵌套

字典中可以进行多维嵌套,如字典套字典,字典套元组,字典套列表等:

  1. dic = {
  2. "k1": [1, 2, 3],
  3. "k2": (1, 2, 3),
  4. "k3": {
  5. "k3-1": 1,
  6. "k3-2": 2,
  7. },
  8. }

类型转换

字典可以与布尔类型和字符串进行转换,这是最常用的。

  1. dic = {"k1": "v1", "k2": "v2"}
  2. boolDict = bool(dic) # 布尔类型
  3. strDict = str(dic) # 字符串类型
  4. print("值:%r,类型:%r" % (boolDict, type(boolDict)))
  5. print("值:%r,类型:%r" % (strDict, type(strDict)))
  6. # 值:True,类型:<class 'bool'>
  7. # 值:"{'k1': 'v1', 'k2': 'v2'}",类型:<class 'str'>

如果要将字典转换为列表、元组、集合类型,直接转换只会拿到键,并不会拿到值。

尤其注意这一点,但是其实这样用的场景十分少见,记住就行了:

  1. dic = {"k1": "v1", "k2": "v2"}
  2. listDict = list(dic) # 列表类型
  3. tupleDict = tuple(dic) # 元组类型
  4. setDict = set(dic) # 集合类型
  5. print("值:%r,类型:%r" % (listDict, type(listDict)))
  6. print("值:%r,类型:%r" % (tupleDict, type(tupleDict)))
  7. print("值:%r,类型:%r" % (setDict, type(setDict)))
  8. # 值:['k1', 'k2'],类型:<class 'list'>
  9. # 值:('k1', 'k2'),类型:<class 'tuple'>
  10. # 值:{'k2', 'k1'},类型:<class 'set'>

重复key

一个字典中的key必须是唯一的,若不是唯一的则value可能面临被覆盖的危险:

  1. dic = {"name": "云崖", "age": 18, "name": "Yunya"}
  2. print(dic)
  3. # {'name': 'Yunya', 'age': 18}

同理,True和1,False和0也会彼此进行覆盖:

  1. dic = {True: "云崖", "age": 18, 1: "Yunya"}
  2. print(dic)
  3. # {True: 'Yunya', 'age': 18}

如果你对此不了解,建议回退到布尔类型一章节中再次查看True&1andFalse&0之间的关系。

[]操纵字典

由于字典并非顺序存储(下面会简单介绍),故不支持索引操作。

但是字典也提供了[]操作语法,它是根据key来操作value的。

增删改查

以下示例展示了如何使用[]对字典中的value进行操纵:

  1. dic = {"k1": "v1", "k2": "v2"}
  2. # 增
  3. dic["k3"] = "v3"
  4. print(dic)
  5. # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
  6. # 删,如果没有该key,则抛出keyError
  7. del dic["k2"]
  8. print(dic)
  9. # {'k1': 'v1', 'k3': 'v3'}
  10. # 改,如果没有该key,则抛出keyError
  11. dic["k3"] = "VV3"
  12. print(dic)
  13. # {'k1': 'v1', 'k3': 'VV3'}
  14. # 查,如果没有该key,则抛出keyError
  15. result = dic["k1"]
  16. print(result)
  17. # v1

多维操作

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

  1. dic = {"k1": [1, 2, 3, 4]}
  2. # 取出3
  3. result = dic["k1"][2]
  4. print(result)
  5. # 3
  6. # k1的列表,添加元素 "A"
  7. dic["k1"].append("A")
  8. print(dic)
  9. # {'k1': [1, 2, 3, 4, 'A']}

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

  1. dic = {
  2. "k1":{
  3. "k1-1":{
  4. "k1-2":{
  5. "k1-3":"HELLO,WORLD",
  6. }
  7. }
  8. }
  9. }
  10. # 拿到 k1-3 对应的value
  11. result = dic["k1"]["k1-1"]["k1-2"]["k1-3"]
  12. print(result)
  13. # HELLO,WORLD

解构语法

**语法

**语法用于将字典中的k:v全部提取出来。

我们可以利用该语法的特性来对字典进行合并,将两个旧字典合并成一个新字典:

  1. dic_1 = {"d1k1": "A", "d1k2": "B"}
  2. dic_2 = {"d2k1": "C", "d2k2": "D"}
  3. result = {**dic_1, **dic_2}
  4. print(result)
  5. # {'d1k1': 'A', 'd1k2': 'B', 'd2k1': 'C', 'd2k2': 'D'}

解构赋值

字典支持平行变量赋值操作吗?当然可以!但是这样只会拿到字典的key:

  1. dic = {"k1": "v1", "k2": "v2"}
  2. first, last = dic
  3. print(first)
  4. print(last)
  5. # k1
  6. # k2

有办法拿到value么?借助字典的values()方法即可做到,它的本质是将value全部提取出来,组成一个可迭代对象:

  1. dic = {"k1": "v1", "k2": "v2"}
  2. first, last = dic.values()
  3. print(first)
  4. print(last)
  5. # v1
  6. # 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类型的值。

  1. dic = {"name": "云崖", "age": 18}
  2. print(len(dic))
  3. # 2 一组键值对被视为一个数据项,故2组键值对长度为2

Python在对内置的数据类型使用len()方法时,实际上是会直接的从PyVarObject结构体中获取ob_size属性,这是一种非常高效的策略。

PyVarObject是表示内存中长度可变的内置对象的C语言结构体。

直接读取这个值比调用一个方法要快很多。

get()

使用get()方法获取字典key对应的value,相比于[]操作更加的人性化,因为[]一旦获取不存在的key则会抛出异常,而该方法则是返回None。

  1. dic = {"name": "云崖", "age": 18}
  2. username = dic.get("name")
  3. userhobby = dic.get("hobby")
  4. print("用户姓名:",username)
  5. print("用户爱好:",userhobby)
  6. # 用户姓名: 云崖
  7. # 用户爱好: None

setdefault()

使用setdefault()方法来获取字典key对应的value,如该字典中不存在被获取的key则会进行新增k:v,并返回v。

返回字典原有的value或者新设置的k:v。

  1. dic = {"name": "云崖", "age": 18}
  2. # 字典有name,则取字典里的name
  3. username = dic.setdefault("name","云崖先生")
  4. # 字典没有hobby,则设置hobby的value为足球与篮球并返回
  5. userhobby = dic.setdefault("hobby","足球与篮球")
  6. print("用户姓名:",username)
  7. print("用户爱好:",userhobby)
  8. # 用户姓名: 云崖
  9. # 用户爱好: 足球与篮球

update()

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

返回None。

  1. dic = {"name": "云崖", "age": 18}
  2. dic.update(
  3. {"hobby": ["篮球", "足球"]}
  4. )
  5. print(dic)
  6. # {'name': '云崖', 'age': 18, 'hobby': ['篮球', '足球']}

pop()

使用pop()方法删除该字典中的键值对,如果不填入参数key或者key不存在则抛出异常。

返回被删除的value。

  1. dic = {"name": "云崖", "age": 18}
  2. result = dic.pop("age")
  3. print(result)
  4. print(dic)
  5. # 18
  6. # {'name': '云崖'}

keys()

返回一个可迭代对象,该可迭代对象中只存有字典的所有key。

Python2中返回的是列表,Python3中返回的是可迭代对象。

  1. dic = {"name": "云崖", "age": 18}
  2. key_iter = dic.keys()
  3. print(key_iter)
  4. # dict_keys(['name', 'age'])

values()

返回一个可迭代对象,该可迭代对象中只存有字典的所有value。

Python2中返回的是列表,Python3中返回的是可迭代对象。

  1. dic = {"name": "云崖", "age": 18}
  2. value_iter = dic.values()
  3. print(value_iter)
  4. # dict_values(['云崖', 18])

items()

返回一个可迭代对象,该可迭代对象中存有字典中所有的key与value,类似于列表套元组。

Python2中返回的是二维列表,Python3中返回的是可迭代对象。

  1. dic = {"name": "云崖", "age": 18}
  2. items_iter = dic.items()
  3. print(items_iter)
  4. # dict_items([('name', '云崖'), ('age', 18)])

clear()

清空当前字典。

返回None。

  1. dic = {"name": "云崖", "age": 18}
  2. dic.clear()
  3. print(dic)
  4. # {}

其他方法

方法 返回值 描述
popitem() (k, v) 随机删除一组键值对,并将删除的键值放到元组内返回
fromkeys(iter,value) dict 第一个参数是可迭代对象,其中每一个元素都为新生成字典的key,第二个参数为同一的value值

示例演示:

  1. dic1 = dict(k1="v1", k2="v2", k3="v3", k4="v4")
  2. print(dic1.popitem())
  3. # ('k4', 'v4')
  4. dic2 = dict.fromkeys([1, 2, 3, 4], None)
  5. print(dic2)
  6. # {1: None, 2: None, 3: None, 4: None}

原理浅析

高效查找

为什么要有字典这种数据结构?

如果对一个无序的列表查找其中某一个value(不能进行排序),必须经过一个一个的遍历,速度会很慢。

  1. [3, 2, 8, 9, 11, 13]
  2. # 如果要获取数据项11,必须经过5次查找

有没有一种办法,能够让速度加快?

为了不违背不能排序的前提,我们只能在列表存入value的时候做文章。

我们可以每个value都造一个独一无二的身份标识,根据这个身份标识符计算出value需要插入到列表的索引位置。

在取的时候同理,通过身份标识符直接就可以拿到value所在列表的索引值,无疑速度会快很多。

一个小总结:

  • 有一个身份标识,身份标识必须是唯一的
  • 提供一个根据身份标识计算出插入位置的算法

回到字典的本质,字典的key就是value的身份标识,而根据key计算出插入位置的算法被封装在了hash()函数中,这个算法也被称之为hash算法。

为什么key必须是唯一的,参照下面这个示例:

  1. ["k1", "k2", "k3", "k4", "k5", "k6"]
  2. [ 3, 2, 8, 9, 11, 13]
  • 假如k5变成了k6,那么就有2个k6对应2个不同的value
  • 这么做的后果就是,使用k6获取value的时候,根本不知道你需要的value是哪一个

所以,干脆Python规定,key必须是不可变类型!如果有重复则新的覆盖旧的。

hash()过程

如何通过hash()函数,确定value的插入位置?

实际上每个键值对在存入字典之前,都会通过hash()函数对key计算出一个hash值(也被称为散列值):

  1. >>> hash("k1")
  2. 7036545863130266253

而字典的底层结构是由一个2维数组嵌套组成的,也被称为散列表、hash表。

如下所示,每次创建字典的时候,字典都会初始化生成一个固定长度且内容全是空的2维数组,Python内部生成的散列表长度为8(可参见PyDictObject结构体源码):

  1. [

  2. [空, 空, 空], index: 0
  3. [空, 空, 空], index: 1
  4. [空, 空, 空], index: 2
  5. [空, 空, 空], index: 3
  6. [空, 空, 空], index: 4
  7. [空, 空, 空], index: 5
  8. [空, 空, 空], index: 6
  9. [空, 空, 空] index: 7
  10. ]

①:存放根据key计算出的hash值

②:存放key的引用

③:存放value的引用

现在,我们要存储name:yunya的键值对,对name计算hash值:

  1. >>> hash("name")
  2. 3181345887314224636

用计算出的hash值与散列表长度进行求余运算:

  1. >>> 3181345887314224636 % 8
  2. 4

得到结果是4,就在散列表4的索引位置插入:

  1. [

  2. [空, 空, 空], index: 0
  3. [空, 空, 空], index: 1
  4. [空, 空, 空], index: 2
  5. [空, 空, 空], index: 3
  6. [3181345887314224636, "name"的引用, "yunya"], index: 4
  7. [空, 空, 空], index: 5
  8. [空, 空, 空], index: 6
  9. [空, 空, 空] index: 7
  10. ]

再次插入age:18,并用计算出的hash值与散列表长度进行求余运算:

  1. >>> hash("age")
  2. 7064862892218627464
  3. >>> 7064862892218627464 % 8
  4. 0

得到的结果是0,就在散列表0的索引位置插入:

  1. [

  2. [7064862892218627464, "age"的引用, 18], index: 0
  3. [空, 空, 空], index: 1
  4. [空, 空, 空], index: 2
  5. [空, 空, 空], index: 3
  6. [3181345887314224636, "name"的引用, "yunya"], index: 4
  7. [空, 空, 空], index: 5
  8. [空, 空, 空], index: 6
  9. [空, 空, 空] index: 7
  10. ]

可以看见,这个2维数组不是按照顺序进行插入的,总有一些空的位置存在,该数组也被称为稀松数组。

由于数组是稀松的,所以dict不支持范围获取(能获取到空值),但单点存取的速度很快。

读取的时候也同理,但是Python的hash函数底层实现是否真的利用hash值对稀松数组长度进行简单的求余运算,这个还有待商榷。

因为hash算法的实现有很多种,长度求余只是最为简单的一种而已,这里用作举例,如果想具体了解其算法可以查看Python源码,PyDictObject.c中的perturb。

散列冲突

现在,我们的这个散列表中0和4的索引位置都已经存在数据了。

如果现在存入一个teacher:wang,那么结果会是怎么样?

  1. >>> hash("teacher")
  2. 4789346189807557228
  3. >>> 4789346189807557228 % 8
  4. 4

可以发现,teacher的hash值求余算结果也是4,这个时候就会发生散列冲突。

最常见的做法是,向后挪!因为索引5的位置是空的,我们可以将这个键值对插入到索引5的位置:

  1. [

  2. [7064862892218627464, "age"的引用, 18], index: 0
  3. [空, 空, 空], index: 1
  4. [空, 空, 空], index: 2
  5. [空, 空, 空], index: 3
  6. [3181345887314224636, "name"的引用, "yunya"], index: 4
  7. [4789346189807557228, "teacher"的引用, "wang"], index: 5
  8. [空, 空, 空], index: 6
  9. [空, 空, 空] index: 7
  10. ]

这种查找空位的方法叫做开放定址法(openaddressing),向后查找也被称为线性探测(linearprobing)。

如果此时又插入一个数据项,最后key的插入索引位置也是4,则继续向后查找空位,如果查找到7还是没有空位,又从0开始找。

上述方法是解决散列冲突的基础方案,当然也还有更多的其他解决方案,这里再说就过头了,放在后面数构一章中再进行介绍吧。

扩容机制

Python的dict会对散列表的容量做出判定。

当容量超过三分之二时,即进行扩容(resize)机制。

如果散列表大小为8,在即将插入第3个键值对时进行扩容,扩容策略为已有散列表键值对个数 * 2。

即散列表大小扩展为12。

如果整个散列表已有键值对个数达到了50000,则扩容策略为已有散列表键值对个数 * 4。

此外,dict只会进行扩容,不会进行缩容,如果删除了1个键值对,其内存空间占用的位置并不会释放。

不同的key优化策略

整形是其本身

整形的hash值是其本身:

  1. >>> hash(1)
  2. 1
  3. >>> hash(2)
  4. 2
  5. >>> hash(3)
  6. 3
  7. >>> hash(10000)
  8. 10000

加盐策略

在Python3.3开始,str、bytes、datetime等对象在计算散列值的时候会进行加盐处理。

这个盐引用内部的一个常量,该常量在每次CPython启动时会生成不同的盐值。

所以你会发现每次重启Python3.3以后的解释器,对相同字符串进行hash()求散列值得出的结果总是不一样的:

  1. $ python3
  2. Python 3.6.8 (v3.6.8:3c6b436a57, Dec 24 2018, 02:04:31)
  3. [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
  4. Type "help", "copyright", "credits" or "license" for more information.
  5. >>> hash("k1")
  6. 8214688532022610754
  7. >>> exit()
  8. $ python3
  9. Python 3.6.8 (v3.6.8:3c6b436a57, Dec 24 2018, 02:04:31)
  10. [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
  11. Type "help", "copyright", "credits" or "license" for more information.
  12. >>> hash("k1")
  13. -7444020267993088839
  14. >>> exit()

再看Python2.7,由于没有加盐策略,所以每次重启得到的hash结果是相同的:

  1. $ python
  2. Python 2.7.10 (default, Feb 22 2019, 21:55:15)
  3. [GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
  4. Type "help", "copyright", "credits" or "license" for more information.
  5. >>> hash("k1")
  6. 13696082283123634
  7. >>> exit()
  8. $ python
  9. Python 2.7.10 (default, Feb 22 2019, 21:55:15)
  10. [GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
  11. Type "help", "copyright", "credits" or "license" for more information.
  12. >>> hash("k1")
  13. 13696082283123634
  14. >>> 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中的字典:

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

再来看3.6中的字典:

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

果然!它确实变的有序了,关于具体细节,可以参照这封邮件,已经表述的很清楚了,下面做一个简单的示例。

首先,以前的散列表就是一个单纯的稀松二维数组:

  1. [
  2. [空, 空, 空], index: 0
  3. [空, 空, 空], index: 1
  4. [空, 空, 空], index: 2
  5. ...
  6. ]

键值对的读取顺序来源与填加顺序。

索引靠前的会被先遍历拿到,索引靠后只能后被遍历出来。

如果这个散列表长度为8,前7个都没有数据项存入,仅有8才有,那么遍历完整个散列表需要8次:

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

而Python3.6之后,又新增了一个顺序数组,该数组与散列表的长度相等,初始均为8,并且会跟随散列表的扩容而进行扩容,如下示例初始状态:

  1. [None, None, None, ...]
  2. [
  3. [空, 空, 空], index: 0
  4. [空, 空, 空], index: 1
  5. [空, 空, 空], index: 2
  6. ...
  7. ]

如果说第1个键值对,被插入到散列表索引1的位置,那么在顺序数组中,则在索引0处记录下该键值对被插入在散列表中的位置(1),如下图所示:

  1. [1, None, None, ...]
  2. [
  3. [空, 空, 空], index: 0
  4. [hash值, key的引用, value的引用], index: 1
  5. [空, 空, 空], index: 2
  6. ...
  7. ]

如果第2个键值对,被插入到散列表索引0的位置,那么在顺序数组中,则在索引1处记录下该键值对被插入在散列表中的位置(0),如下图所示:

  1. [1, 0, None, ...]
  2. [
  3. [hash值, key的引用, value的引用], index: 0
  4. [hash值, key的引用, value的引用], index: 1
  5. [空, 空, 空], index: 2
  6. ...
  7. ]

在遍历的时候,会遍历这个顺序数组,然后通过索引值拿到散列表中对应位置的数据项,如果遍历到的值为None就结束遍历,而不用遍历完整个散列表:

  1. [1, 0, 7, None, None, None, None, None]
  2. [
  3. [hash值, key的引用, value的引用], index: 0
  4. [hash值, key的引用, value的引用], index: 1
  5. [空, 空, 空], index: 2
  6. ...
  7. [hash值, key的引用, value的引用], index: 7
  8. ]

类似于:

  1. hashTableOrderArray = [1, 0, 7, None, None, None, None, None]
  2. hashTable = [
  3. ["hash", "k2", "v2"],
  4. ["hash", "k1", "v1"],
  5. [None, None, None],
  6. [None, None, None],
  7. [None, None, None],
  8. [None, None, None],
  9. [None, None, None],
  10. ["hash", "k3", "v3"],
  11. ]
  12. n = 0
  13. while n < len(hashTable):
  14. if hashTableOrderArray[n] is not None:
  15. print(hashTable[hashTableOrderArray[n]])
  16. else:
  17. break
  18. n += 1

这样只需遍历3次即可,而如果不用这个顺序数组,则要完整遍历整个散列表,即8次才能拿出所有的键值对。

字典特性

字典特性如下:

  • 字典是一个可变的容器类型
  • 字典内部由散列表组成
  • 字典的单点读写速度很快,但是不支持范围查找
  • 字典的key必须是不可变的
  • 字典在3.6之后变得有序了,这样做提升了遍历效率

参考文章:PyDictObject实现

老Python总结的字典相关知识的更多相关文章

  1. Python静态网页爬虫相关知识

    想要开发一个简单的Python爬虫案例,并在Python3以上的环境下运行,那么需要掌握哪些知识才能完成一个简单的Python爬虫呢? 爬虫的架构实现 爬虫包括调度器,管理器,解析器,下载器和输出器. ...

  2. 初识python 字符串 列表 字典相关操作

    python基础(一): 运算符: 算术运算: 除了基本的+ - * / 以外,还需要知道 :  // 为取整除 返回的市商的整数部分 例如: 9 // 2  ---> 4  , 9.0 //  ...

  3. python多线程、多进程相关知识

    Queue Queue用于建立和操作队列,常和threading类一起用来建立一个简单的线程队列. 首先,队列有很多种,根据进出顺序来分类,可以分成 Queue.Queue(maxsize) FIFO ...

  4. 【Python五篇慢慢弹(5)】类的继承案例解析,python相关知识延伸

    类的继承案例解析,python相关知识延伸 作者:白宁超 2016年10月10日22:36:57 摘要:继<快速上手学python>一文之后,笔者又将python官方文档认真学习下.官方给 ...

  5. Python 数据分析(二 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

    Python 数据分析(二) 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识 第1节 groupby 技术 第2节 数据聚合 第3节 分组级运算和转换 第4 ...

  6. python实现单例模式的三种方式及相关知识解释

    python实现单例模式的三种方式及相关知识解释 模块模式 装饰器模式 父类重写new继承 单例模式作为最常用的设计模式,在面试中很可能遇到要求手写.从最近的学习python的经验而言,singlet ...

  7. 《Python网络编程》学习笔记--从例子中收获的计算机网络相关知识

    从之前笔记的四个程序中(http://www.cnblogs.com/take-fetter/p/8278864.html),我们可以看出分别使用了谷歌地理编码API(对URL表示地理信息查询和如何获 ...

  8. python数组相关知识

    1.np中的reshape函数,可以把矩阵重新划分成m行n列. arange(n)可以把 [0,n-1]装入数组中,一定要注意的是img.reshape()并不会改变原来的数组,所以需要另外新建一个数 ...

  9. 使用Nginx+uwsgi在亚马逊云服务器上部署python+django项目完整版(二)——部署配置及相关知识

    ---恢复内容开始--- 一.前提: 1.django项目文件已放置在云服务器上,配置好运行环境,可正常运行 2.云服务器可正常连接 二.相关知识 1.python manage.py runserv ...

随机推荐

  1. Layui 源码浅读(模块加载原理)

    经典开场 // Layui ;! function (win) { var Lay = function () { this.v = '2.5.5'; }; win.layui = new Lay() ...

  2. Omega System Trading and Development Club内部分享策略Easylanguage源码 (第二期)

    更多精彩内容,欢迎关注公众号:数量技术宅,也可添加技术宅个人微信号:sljsz01,与我交流. 我们曾经在前文(链接),为大家分享我们精心整理的私货:"System Trading and ...

  3. 剑指 Offer 04. 二维数组中的查找 (思维)

    剑指 Offer 04. 二维数组中的查找 题目链接 本题的解法是从矩阵的右上角开始寻找目标值. 根据矩阵的元素分布特性, 当目标值大于当前位置的值时将row行号++,因为此时目标值一定位于当前行的下 ...

  4. redis基础:redis下载安装与配置,redis数据类型使用,redis常用指令,jedis使用,RDB和AOF持久化

    知识点梳理 课堂讲义 课程计划 1. REDIS 入 门 (了解) (操作)   2. 数据类型 (重点) (操作) (理解) 3. 常用指令   (操作)   4. Jedis (重点) (操作) ...

  5. 【pytest官方文档】解读fixtures - 7. Teardown处理,yield和addfinalizer

    当我们运行测试函数时,我们希望确保测试函数在运行结束后,可以自己清理掉对环境的影响. 这样的话,它们就不会干扰任何其他的测试函数,更不会日积月累的留下越来越多的测试数据. 用过unittest的朋友相 ...

  6. Android的Proxy/Delegate Application框架

    转自:http://blogs.360.cn/360mobile/2013/11/25/proxydelegate-application/#comment-77 有的时候,为了实现一些特殊需求,如界 ...

  7. HDU_3333 Turing Tree 【线段树 + 离散化】

    一.题目 Turing Tree 二.分析 这题主要还是在区间的处理上. 为了保证区间内的数没有重复的,那么可以对区间按右端点从小到大排序,这样对原数组处理时,尽量保证不重复的元素靠右(可以假设右端点 ...

  8. 常用开发库 - 告別BeanUtils拷贝,MapStruct工具库最全详解

    常用开发库 - MapStruct工具库详解 MapStruct是一款非常实用Java工具,主要用于解决对象之间的拷贝问题,比如PO/DTO/VO/QueryParam之间的转换问题.区别于BeanU ...

  9. A Color Game

    题目大意:  给定一个只包含七种字母的字符串,如果满足一段连续相同的字符长度大于等于K那么即可消除,问最后能不能变为空字符. 题解:很明显是用区间dp来解决,我们设dp[l][r][k]代表的是在[l ...

  10. [源码分析] 消息队列 Kombu 之 mailbox

    [源码分析] 消息队列 Kombu 之 mailbox 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Komb ...