手撕deque源码,解密双端队列的设计艺术

我们已经学习了 list 对象的内部结构,知道它底层是用动态数组实现的。在 list 头部进行插入或删除,都要挪动其后的所有数据,性能非常差!因此,我们不能将 list 对象作为队列使用。

好在 Python 标准库提供了另一种对象—— deque ,很好地补全了 list 的短板。deque 是一种类似 list 的线性表,但它在两端插入删除数据的时间复杂度都是  ,因而可以作为队列来使用。

from collections import deque

# 创建一个deque对象作为队列
q = deque()

# 数据入队:将其插到末尾
q.append(data)

# 数据出队:从头部弹出
data = q.popleft()

您可能会问了,deque 到底跟 list 有何不同?为何两端都能同时支持  的插入删除呢?

内部结构

这其中的秘密还得到源码中寻找。Python标准库位于源码中的 Lib 目录,collections 模块源码位于 Lib/collections 目录,模块入口文件为 Lib/collections/__init__.py 。打开 __init__.py 可以看到这段代码( 29~34 行):

try:
    from _collections import deque
except ImportError:
    pass
else:
    _collections_abc.MutableSequence.register(deque)

由此可见,deque 是在另一个模块 _collections 中实现的。我们在 Lib 中并没有找到 _collections模块,它很有可能是一个用 C 语言实现的模块。滋补小铺我们回到 Modules 目录看一看,果然找到了 Modules/_collectionsmodule.c 。

这个源码文件就是 deque 实现之所在,不难找到 deque 对象底层结构体定义( 71~96 行):

typedef struct BLOCK {
    struct BLOCK *leftlink;
    PyObject *data[BLOCKLEN];
    struct BLOCK *rightlink;
} block;

typedef struct {
    PyObject_VAR_HEAD
    block *leftblock;
    block *rightblock;
    Py_ssize_t leftindex;       /* 0 <= leftindex < BLOCKLEN */
    Py_ssize_t rightindex;      /* 0 <= rightindex < BLOCKLEN */
    size_t state;               /* incremented whenever the indices move */
    Py_ssize_t maxlen;          /* maxlen is -1 for unbounded deques */
    PyObject *weakreflist;
} dequeobject;

这里的源码以 Python 3.7.4 版本为例,不同版本可能略有差异。

dequeobject 结构体便是 deque 实例对象的底层肉身,可以看到 deque 将自己保存的元素组织成链表结构,难怪在两端增删元素都很快!跟普通链表节点只保存一个元素不同,deque 的链表节点可以保存很多元素。

block 结构体就是 deque 内部的链表节点,顾名思义,它是一个内存块。block 结构体非常简单,只有 3 个字段:

  • leftlink ,左向指针,指向左边(更接近头部)的内存块;

  • rightlink ,右向指针,指向右边(更接近尾部)的内存块;

  • data ,对象指针数组,deque 中的元素就保存在这里;

内存块 block 由 leftlink 和 rightlink 字段串联,组成一个双向链表。block 内存块中的 data 数组,长度由 BLOCKLEN 宏定义决定( 第 21 行 ):

#define

你可能感兴趣的:(编程,python)