• vector的增长模式


    引言

    我们都知道vector对象是动态存储的,从这一点看有点像链表,可以动态的增加或减少元素。我们也知道链表中是有指针变量,专门用于存储上一个和下一个元素的地址。正是因为这两个指针的存在,我们才能做到动态的存储数据,即不用像数组那样必须事先申请好空间。链表的缺点就是不能够快速的随机访问其中元素,必须通过指针层层查找。

    但是,vector既可以实现动态存储数据,而且支持快速随机访问(用下标或者指针访问元素)。对于能够用下标查找的数据类型,其存储方式必定是连续的,即每个元素紧挨着前一个元素存储。

    这样对于vector来说,就会出现一个问题,我在初始定义vector的时候该给其申请多少内存空间?如果申请的很小,那么我来了新的数据改存放在哪里?如果申请的很大,我用不完,那岂不是很浪费内存空间?

    当然对于vector这种标准库类型,通常我们只关心如何使用它,而不关心其实如何实现的。不过对于其在存储空间的实现方式还是了解一下比较好。

    我们知道容器中元素连续存储,且容器大小是可变的,考虑向vector中添加元素会发生什么?如果没有空间容纳新的元素,容器不可能简单的将其添加到内存的其他位置,因为vector的元素必须连续存储。因此容器必须分配新的空间来保存已用的元素和新的元素。将已有元素从旧位置移动到新空间,然后添加新元素,释放旧空间。如果说我们每添加一个新的元素就执行一次这样的操作,显然性能会慢到我们不可接受。

    为了避免上面的代价,标准库采用了可以减少容器空间重新分配的策略。当不得不获取新的内存空间时,vector的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,从而用来保存更多新的元素。这样,就不需要每次添加新的元素都重新分配容器的内存空间了。

    在插入新元素时,若遇到已分配容量不足的情况,会自动拓展容量大小,而这个拓展容量的过程为:

    • 开辟另外一块更大的内存空间,该空间大小通常为原空间大小的两倍(理论分析上是1.5最好,但从时间和空间上综合考虑,一般取2);
    • 将原内存空间中的数据拷贝到新开辟的内存空间中;
    • 析构原内存空间的数据,释放原内存空间,并调整各种指针指向新内存空间。

    vector类型提供了一些成员函数,允许我们与它的现实中内存分配部分互动。

    c.capacity()     不重新分配内存空间的话,c可以保存多少元素

    c.reserve(n)     分配至少能容纳n个元素的内存空间

    c.shrink_to_fit()  将capacity()减少为size()相同大小,size()为vector已经保存元素个数。

    预先保留空间—reserve() 和 resize() 函数。

    reserve()是容器预留空间,但并不真正创建元素对象,在创建对象之前,不能引用容器内的元素,因此当加入新的元素时,需要用push_back()/insert()函数。
    resize是改变容器的大小,并且创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。
    再者,两个函数的形式是有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小,第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。下面是这两个函数使用例子:

    vector<int> myVec;
     
    myVec.reserve( 100 );     // 新元素还没有构造,
     
                              // 此时不能用[]访问元素
     
    for (int i = 0; i < 100; i++ )
     
    ...{
     
         myVec.push_back( i ); //新元素这时才构造
     
    }
     
    myVec.resize( 102 );      // 用元素的默认构造函数构造了两个新的元素
     
    myVec[100] = 1;           //直接操作新元素
     
    myVec[101] = 2;

    空间归还—shrink_to_fit()

    如代码

    int main()
    {
        vector<int> v1;
        for (int i = 1; i < 11; i++)
        {
            v1.push_back(i);
        }
        cout << "capacity = " << v1.capacity() << "    " << "size = " << v1.size() << endl;
        v1.shrink_to_fit();
        cout << v1.capacity() << endl;
     
    }

    从结果中可以看出,当 内存用不完的时候,可以请求系统归还内存,最后,使得 capacity()==size()

    空间释放—swap()

    需要注意的是,如果采用vector存储数据,执行clear()并不能释放内存,它只是清空了数据,其实这个问题在《Effective STL》中的“条款17”已经指出了

    当vector、string大量插入数据后,即使删除了大量数据(或者全部都删除,即clear) 并没有改变容器的容量(capacity),所以仍然会占用着内存。 为了避免这种情况,我们应该想办法改变容器的容量使之尽可能小的符合当前 数据所需(shrink to fit)

    《Effective STL》给出的解决方案是:

    vector<type> v;
    //.... 这里添加许多元素给v
    //.... 这里删除v中的许多元素
    vector<type>(v).swap(v);
    //此时v的容量已经尽可能的符合其当前包含的元素数量
    //对于string则可能像下面这样
    string(s).swap(s);

    下面是网友的测试代码

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    vector <string> v;
    char ch;
    
    int main ()
    {
    
        for(int i=0; i<1000000; i++)
            v.push_back("abcdefghijklmn");
        cin >> ch;
        // 此时检查内存情况 占用54M
    
        v.clear();
        cin >> ch;
        // 此时再次检查, 仍然占用54M
    
        cout << "Vector 的 容量为" << v.capacity() << endl;
        // 此时容量为 1048576
    
        vector<string>(v).swap(v);
    
        cout << "Vector 的 容量为" << v.capacity() << endl;
        // 此时容量为0
        cin >> ch;
        // 检查内存,释放了 10M+ 即为数据内存
        return 0;
    }

    当然,上面这种方法虽然释放了内存,但是同时也增加了拷贝数据的时间消耗。 不过一般需要重新调整容量的情况都是 vector本身元素较少的情况,所以 时间消耗可以忽略不计。

    因此建议以后大家都将调用 clear() 改为 swap() 吧。


    参考文献

    https://blog.csdn.net/autocyz/article/details/45194001

    https://blog.csdn.net/dengheCSDN/article/details/78984842

    https://lgcagithub.github.io/2016/08/20/cpp-std-vector-grow/

    http://www.drdobbs.com/c-made-easier-how-vectors-grow/184401375

    https://www.zhihu.com/question/36538542

    http://blog.jobbole.com/37700/

  • 相关阅读:
    linux离线安装nodejs ,配置环境,离线配置全局包
    前端项目路由使用browserHistory不能刷新,页面刷新错误,
    手机uc浏览器打不开本地网页,网页一片空白
    display:flex兼容性,
    react生产环境样式丢失问题 在浏览器中看到标签有类名,但没有样式
    redux使用redux-thunk中间件处理异步状态管理请求
    配置Redux DevTools
    flex-grow属性在文本过长时会超出父窗体长度
    react配置代理,解决跨域
    react修改状态
  • 原文地址:https://www.cnblogs.com/ovs98/p/9908847.html
Copyright © 2020-2023  润新知