STL源码剖析(4)序列式容器2
deque
- deque是双端队列。
- 它可以操作头和尾部的元素。
- deque内部是一块一块连续存储空间链成的。也就是说,部分空间是连续的。
- deque不像vector那样,它没有capacity的概念。
deque的底层结构
deque底层是一段一段的连续存储空间链接起来的。每一段连续的空间是一块缓冲区,也称为一个节点。缓冲区是保存数据的地方。
所有的缓冲区被一个叫做map小块连续空间保存。map是一个指针数组,每个指针指向了一块缓冲区。
这样设计的好处是,当我们需要在头部插入的时候,不用移动所有的数据。如果第一个缓冲块没满,那么直接添加就行了,复杂度是O(1),如果满了就插一个缓冲块,修改map就行了,复制移动map的花销相对而言要小得多。
缺点就是会增加迭代器的复杂程度。Go中的map好像也是以这种方式存起来的。
deque模板有3个参数,第一个和第二个跟普通的容易一样,指代容器的类型和分配器。第三个参数指代的是每个缓冲区的大小。
deque的迭代器
deque的迭代器就麻烦很多了。因为:
- 迭代器必须知道缓冲区的位置
- 迭代器必须知道是否到达了缓冲区的最后一个位置
这样的话,迭代器需要记住两个东西,一个是缓冲区的大小,这样就能知道当前是不是走到了缓冲区尾部。第二个是当前属于哪个缓冲区,这样就知道要跳转到的下一个缓冲区是哪个。
deque的内存管理
deque的内存管理略微麻烦一点。因为需要管理map和缓冲区。deque定义了两个专属的分配器,data_allocator管理缓冲区大小,map_allocator管理map的大小。
管理map:
- 通常map的大小会比实际的缓冲区个数大2个,头一个尾一个,方便扩展。
- 当map上的节点满了以后,就会申请块新的内存,并将旧的数据复制过去。这个也好理解,map本身就是个数组。所以扩容方式也像数组。
管理缓冲区:
- 如果缓冲区满了就直接申请一块新的buffer,并将节点个数加一个。
stack和queue
这两个很简单,是个适配器。底层就是一个deque或者list,只是接口做了些包装而已。
stack和queue是没有迭代器的。
priority_queue
优先队列的底层是一个大顶堆。这里并不打算详细说明堆是如何构建的,只大概提一点重要的东西。
- 堆的构建是从最后一个非叶子节点开始,依次倒着进行shiftdown操作,shiftdown就是从上往下,依次把小节点给换下去。(因为是倒着弄的,所以保证了每个子树都是大顶堆)
- 堆的插入就是把新元素放到最后,然后shiftup。shiftup就是从下往上找父节点,把小的换下来,大的换上去。
- 堆的删除只能操作堆顶。把最后一个元素与堆顶交换,然后做一次shiftdown。然后长度-1
堆也是没有迭代器的。

浙公网安备 33010602011771号