zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    yolo_to_onnx ValueError: need more tan 1 value to unpack
    yolo_to_onnx killed
    C++ 实现二维矩阵的加减乘等运算
    Leetcode 1013. Partition Array Into Three Parts With Equal Sum
    Leetcode 1014. Best Sightseeing Pair
    Leetcode 121. Best Time to Buy and Sell Stock
    Leetcode 219. Contains Duplicate II
    Leetcode 890. Find and Replace Pattern
    Leetcode 965. Univalued Binary Tree
    Leetcode 700. Search in a Binary Search Tree
  • 原文地址:https://www.cnblogs.com/ovs98/p/9908847.html
Copyright © 2011-2022 走看看