vector的原理与底层实现
重点介绍一下resize()扩容和reserve()两个函数
resize()
resize()扩容的默认构造的方式是0, 之后插入按照1 2 4 8 16 二倍扩容。注(GCC是二倍扩容,VS13是1.5倍扩容。原因可以考虑内存碎片和伙伴系统,内存的浪费)。
扩容后是一片新的内存,需要把旧内存空间中的所有元素都拷贝进新内存空间中去,之后再在新内存空间中的原数据的后面继续进行插入构造新元素,
并且同时释放旧内存空间,并且,由于vector 空间的重新配置,导致旧vector的所有迭代器都失效了。
reserve():
1、reserve只是保证vector的空间大小(_capacity)最少达到它的参数所指定的大小n。在区间[0, n)范围内,预留了内存但是并未初始化
2、只有当所申请的容量大于vector的当前容量capacity时才会重新为vector分配存储空间;小于当前容量则没有影响
3、reserve方法对于vector元素大小没有任何影响,不创建对象。
vector的初始的扩容方式代价太大,初始扩容效率低, 需要频繁增长,不仅操作效率比较低,而且频繁的向操作系统申请内存容易造成过多的内存碎片,
所以这个时候需要合理使用resize()和reserve()方法提高效率减少内存碎片的。
问题一:为什么非要以倍数的形式扩容,而不是以固定值的形式扩容。
倍数方式空间拷贝数据次数
假设vector初始的capacity=10,size=0,总共有100个元素,以2倍的形式增长。换算下来大概是需要进行5次扩容。这样的话,相当于旧空间数据到原空间数据的拷贝有5次。
固定个数方式空间拷贝数据次数
假设vector初始的capacity=10,size=0,总共有100个元素,每次以10个元素个数的形式增长。(每次新增10个空间)。所以这次的扩容次数为 100/10 = 10次,也就是说,
插入100白个元素,需要扩容10次。
但是,如果n=1000的情况下, 以个数形式进行扩容就不能在为10了,否则拷贝空间次数将会太多
有的小伙伴要问:但是可以取100呀,想想,如果n=10的情况下,取100又不太合适,所以,以个数的形式来进行扩容显然不符合所用n的取值。
问题二:为什么每次增长是1.5倍或者2倍形式,而不是3倍或者4倍形式增长。
如果以大于2倍的方式来进行扩容,下一次申请空间会大于之前申请所有空间的总和,这样会导致之前的空间不能再被重复利用,这样是很浪费空间的操作。
所以,如果扩容一般基于(1, 2] 之间进行扩容
举个例子:
2倍扩容
1,2,4,8,16,32,...
1.5倍扩容
4,6,9,14,21,31.5,...
2倍扩容的时候,下一次扩容的内存总是比之前释放过的内存和都大,即之前释放过的内存在之后的扩容中不肯能被使用
1.5倍扩容的时候,以上面的例子,第6次扩容的时候,就可以重复利用之前释放过的内存,31.5<4+6+9+14
所以为了提高内存利用率,减少扩容次数,每次扩容的倍数应该在[1.5,2]之间更合适
vector实现代码
#ifndef _MY_VECTOR_HPP_ #define _MY_VECTOR_HPP_ template<typename T> class MyVector { public: // 构造函数 MyVector() { //这里默认数组大小为10 //但是vector文件中默认构造的方式是0, 之后插入按照1 2 4 8 16 二倍扩容。注(GCC是二倍扩容,VS13是1.5倍扩容 data = new T[10]; _capacity = 10; _size = 0; } ~MyVector() { delete[] data; } //reserve只是保证vector的空间大小(_capacity)最少达到它的参数所指定的大小n。在区间[0, n)范围内,预留了内存但是并未初始化 void reserve(size_t n) { if(n>_capacity) { data = new T[n]; _capacity = n; } } //向数组中插入元素 void push_back(T e) { //如果 当前容量已经不够了, 重新分配内存, 均摊复杂度O(1) if (_size == _capacity) { resize(2 * _capacity); } data[_size++] = e; } //删除数组尾部的数据,同时动态调整数组大小,节约内存空间 T pop_back() { T temp = data[_size]; _size--; //如果 容量有多余的,释放掉 if (_size == _capacity / 4) { resize(_capacity / 2); } return temp; } //获取当前数组中元素的个数 size_t size() { return _size; } //判断数组是否为空 bool empty() { return _size==0?1:0; } //重载[]操作 int &operator[](int i) { return data[i]; } //获取数组的容量大小 size_t capacity() { return _capacity; } //清空数组,只会清空数组内的元素,不会改变数组的容量大小 void clear() { _size=0; } private: T *data; //实际存储数据的数组 size_t _capacity; //容量 size_t _size; //实际元素个数 //扩容 void resize(int st) { //重新分配空间,在栈区新开辟内存,然后将以前数组的值赋给他,删除以前的数组 T *newData = new T[st]; for (int i = 0; i < _size; i++) { newData[i] = data[i]; } //实际使用时是清除数据,但不会释放内存 delete[] data; data = newData; _capacity = st; } }; #endif //_MY_VECTOR_HPP_
测试代码
#include <iostream> #include<string> #include "MyVector.hpp" using namespace std; int main() { int size=11; MyVector<int> p; p.reserve(5); cout<<"size="<<p.size()<<"capacity="<<p.capacity()<<endl; for (int i = 0; i < size; i++) { p.push_back(i); } for(int i=0;i<size;i++) { cout<<p[i]<<' '; } cout<<endl; printf("size=%d capcity=%d\n",p.size(),p.capacity()); // MyVector<string>pp; // for(int i=0;i<5;i++) // { // pp.push_back("str"); // } // cout<<pp.size()<<" "<<pp.capacity()<<endl; cout<<endl; return 0; }