std::deque
是一个双端队列,它的内存管理机制与 std::vector
不同,因为 std::deque
不会像 std::vector
一样将所有元素存储在一个连续的内存块中。相反,std::deque
采用了分段存储的方式,它将数据分配到多个固定大小的缓冲区(也叫做缓冲块或块),并通过一个 map(一个指针数组)来管理这些缓冲区。
std::deque
的内存结构
部分源码
template<class T, class Alloc =alloc, size_t BufSiz = 0>
class deque {
public:
typedef T value_type;
typedef _deque_iterator<T, T &, T *, BufSiz> iterator;
protected:
typedef pointer *map_pointer; // T**
protected:
iterator start;
iterator finish;
map_pointer map; // 控制中心,数组中每个元素指向一个buffer
size_type map_size;
public:
iterator begin() { return start; }
iterator end() { return finish; }
size_type size() const { return finish - start; }
// ...
};
- 缓冲区(buffer):
std::deque
将数据分配到多个缓冲区(也叫块)。每个缓冲区可以容纳一定数量的元素(例如 512 或 1024 个元素)。- 每个缓冲区的大小是固定的,而且通常是通过
std::deque
内部的实现来管理。
- map(指针数组):也可以理解为指针的指针
std::deque
使用一个 map 来管理所有的缓冲区。这个 map 实际上是一个指针数组,每个指针指向一个缓冲区(即一个数据块)。- map 的大小会随着数据量的增加而动态扩展。
- map 通常是一个指向缓冲区的指针数组,支持对这些缓冲区的快速访问。且map指向缓冲区的
- start 和 finish:
std::deque
使用start
和finish
来分别指示队列的开始和结束位置。- start:指向 deque 中第一个元素所在的缓冲区。
- finish:指向 deque 中最后一个元素所在的缓冲区,并且指向该缓冲区中的下一个位置。
start
和finish
是指针,指向具体缓冲区中的元素。最后被begin
和end
封装成函数。
std::deque
扩展内存的过程
1. 扩展 map
当 std::deque
中的元素增加到当前 map
容量的极限时,deque 会扩展其 map
。扩展过程通常是通过以下步骤来完成的:
- 动态扩展:当
std::deque
中的元素增加到当前map
容量的极限时,它会分配一个新的、更大的指针数组(map
)。这个新的map
会容纳更多的缓冲区指针。 - 复制数据:在扩展
map
后,deque
会将原来map
中的缓冲区指针复制到新map
中。这使得deque
可以继续存储更多的缓冲区指针。
//STL源码剖析提到的,跳出缓冲区的函数,这里的node就是上面的map,即管控中心
void set_node(map_pointer new_node) {
node = new_node;
first = *new__node;
last = first + difference_type(buffer_size());
)
2. 扩展缓冲区(buffer)
std::deque
的数据是存储在多个缓冲区(数据块)中的。当队列中有足够多的元素时,deque
会分配新的缓冲区并将新的元素存储到该缓冲区。
- 分配新缓冲区:当
std::deque
需要更多空间时,它会分配一个新的缓冲区(或数据块)。这些新的缓冲区被添加到map
中。 - 缓冲区管理:每个缓冲区有一个固定的大小。当
deque
中的元素增加时,如果当前的缓冲区已满,deque
会分配一个新的缓冲区并将新元素添加到该缓冲区中。 - 动态增长:随着
deque
中元素数量的增加,它会持续扩展map
和缓冲区,确保每个缓冲区始终保持足够的空间来存储新的元素。
3. 计算 finish
在 std::deque
中,finish
是指向 deque 中最后一个元素的下一个位置。finish
的计算通常如下:
finish
是指向map
中某个缓冲区的指针,该缓冲区包含当前队列的最后一个元素。- 当元素插入到队列的末尾时,
finish
会向后移动。如果当前缓冲区已满,finish
会指向新的缓冲区,并且新元素将插入到这个新缓冲区中。 finish
是通过 map 中的指针进行间接访问的。如果map
中的某个指针指向一个缓冲区,并且该缓冲区不再能容纳新元素,finish
会被移动到下一个缓冲区。
deque
的内存结构总结
- 数据存储:数据分布在多个缓冲区中,每个缓冲区可以容纳一定数量的元素。这些缓冲区通过一个指针数组(
map
)来管理。 - 扩展缓冲区和map:
- 当需要更多内存时,
std::deque
会分配新的缓冲区并扩展map
,将新的缓冲区指针添加到map
中。 - 缓冲区是固定大小的,因此当缓冲区满时,
std::deque
会分配新的缓冲区。
- 当需要更多内存时,
finish
和start
指针:finish
指向队列的尾部,指向最后一个元素的下一个位置;start
指向队列的头部。
deque
关键成员变量
template <class T, class Alloc>
class deque {
public:
typedef T* iterator;
typedef T& reference;
typedef T* pointer;
typedef size_t size_type;
iterator* map; // 指向缓冲区的指针数组
size_type map_size; // map 中的元素数量(指向缓冲区的指针数)
iterator start; // 指向队列开始部分
iterator finish; // 指向队列结束部分
size_type buffer_size; // 每个缓冲区可以存储的元素数量
};
内存扩展的流程
- 当
deque
需要更多空间时,会扩展map
,即增加更多指向缓冲区的指针。 - 新的缓冲区被分配,并且新的元素会存储到这些缓冲区中。
finish
指针指向当前队列的尾部,并随着元素的插入或删除而移动。
总结
std::deque
采用了分段存储的策略,将数据分配到多个缓冲区中,并通过一个指向缓冲区的指针数组(map
)来管理这些缓冲区。- 每个缓冲区是固定大小的,当某个缓冲区满时,
deque
会分配新的缓冲区,并将新元素添加到新缓冲区中。 std::deque
的内存管理方式通过动态扩展map
和缓冲区来确保容器能够有效地存储元素,并且能够提供快速的访问操作。