• Python中的字典有序无序浅析


    一、前言

    Python在3.5之前无法保证字典遍历时候与元素添加进入字典时候的顺序一致。而在3.6以后,字典中的元素可以有序遍历,并且相对于3.5也做了空间上的优化。

    二、3.5之前

    1、初始化字典

    初始化空字典的时候,首先会在内存中初始化一个二维数据,数组8行,3列。二维数组中,3列依次存储hash值,键的内存指针,值的指针。比如:

    my_dict = {}
    
    # 此时的内存示意图
    [[---, ---, ---],
    [---, ---, ---],
    [---, ---, ---],
    [---, ---, ---],
    [---, ---, ---],
    [---, ---, ---],
    [---, ---, ---],
    [---, ---, ---]]
    

    2、添加元素

    添加元素时候,首先会根据键计算出hash值,然后根据hash值求出存放内存数组中的索引,然后将键的hash值,键值对的地址添加到数组中三列对应位置。比如:

    >>> hash('name')
    12709084984489
    >>> 12709084984489 % 8
    5 # 5即为存放在二维数组中的下标为5的位置
    

    Python自带的hash函数也是计算出来的,每次关闭开启Py之后的hash函数不同,但是同一次执行过程中键计算出来的值是相同的。
    添加多个元素的时候,如果计算出来的hash值冲突,也会采用开放地址等方式处理该问题。比如:

    my_dict['age'] = 26
    my_dict['salary'] = 999999
    此时的内存数组示意图
    [[-4234469173262486640, 指向salary的指针, 指向999999的指针],
    [1545085610920597121, 执行age的指针, 指向26的指针],
    [---, ---, ---],
    [---, ---, ---],
    [---, ---, ---],
    [1278649844881305901, 指向name的指针, 指向kingname的指针],
    [---, ---, ---],
    [---, ---, ---]]
    

    可以看到先添加的name,在添加的age和salary,但是在内存数组中的顺序却与添加顺序不一致,因此在遍历的时候也顺序也会无序。

    3、内存

    对于上述的实现,初始的时候会开辟固定的内存空间,每一行8X3=24字节。当数据超过数组的2/3空间的时候,数组会进行扩容,8行变为16行,变为32行。容量变化的同时,根据hash值计算余数求内存数组对应的下标索引也会出现变化,因此插入新数据的时候如果涉及到扩容,可能需要移动数据,效率较低。

    三、3.6之后

    1、初始化字典

    py3.6之后,初始空字典的时候,底层会有两个数组,一个存放键值对存放内存数组中的索引,一个存放真正的实体。可以看出同样是初始可以存放8个键值对。做了相关的改动和优化。比如

    my_dict = {}
    此时的内存数组示意图
    indices = [None, None, None, None, None, None, None, None]
    entries = []
    

    2、插入数据

    当添加元素的时候,同样会计算hash值,然后取余,不同的是根据取余的结果作为indices数组的索引修改indices数组相应的位置,修改为实际存放数据的第二个数组entries的索引。比如:

    >>> hash('name')
    4193068542476671
    >>> hash('name') % 8
    1  # 修改索引数据indices[1]位置
    
    my_dict['name'] = 'kingname'
    
    此时的内存示意图
    indices = [None, 0, None, None, None, None, None, None]
    # indices[1] = 0 表示真实数据是存放在entries[0]的位置
    entries = [
        [-5954193068542476671, 指向name的指针, 执行kingname的指针]
      ]
    

    当插入多个元素时候,entries二维数组按照顺序存储插入的元素

    my_dict['address'] = 'xxx'
    my_dict['salary'] = 999999
    
    此时的内存示意图
    indices = [1, 0, None, None, None, None, 2, None]
    entries = [
              [-5954193068542476671, 指向name的指针, 执行kingname的指针],
              [9043074951938101872, 指向address的指针,指向xxx的指针],
              [7324055671294268046, 指向salary的指针, 指向999999的指针]
             ]
    

    读取数据时候,根据hash函数计算出hash值,然后求出indices的下标x,根据indices[x]的值就可以读取到真正的键值对,遍历的时候也可以做到 有序遍历。

    3、占用内存

    相比较于3.5之前的版本,空间也做了优化,初始化的时候不需要再固定开辟一个二维数组,只有一个固定长度的indices数组。

  • 相关阅读:
    jQuery笔记之工具方法—Ajax 优化回调地狱
    jQuery笔记之工具方法—高级方法Ajax
    jQuery笔记之 Ajax回调地狱
    jQuery笔记之工具方法extend插件扩展
    jQuery同时监听两个事件---实现同时操控两个按键
    jQuery笔记之工具方法
    肖sir_多测师 _高级讲师 第二个月21讲解jmeter性能测试之安装badboy(002)
    肖sir_多测师 _高级讲师 第二个月21讲解jmeter之实战性能测试(001)
    肖sir_多测师 _高级讲师 第二个月20讲解jmeter之实战操作mysql(004)
    肖sir_多测师 _高级讲师 第二个月20讲解jmeter接口之实战(003)
  • 原文地址:https://www.cnblogs.com/welan/p/15920627.html
Copyright © 2020-2023  润新知