zoukankan      html  css  js  c++  java
  • 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
    ",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;
    }
  • 相关阅读:
    Asp.Net Core使用Nginx实现反向代理
    在Liunx上搭建FTP并配置用户权限
    Asp.Net Core 使用Docker进行容器化部署(二)使用Nginx进行反向代理
    Asp.Net Core 使用Docker进行容器化部署(一)
    .Net Core On Liunx 环境搭建之 Docker 容器和Nginx
    .Net Core On Liunx 环境搭建之安装Mysql8
    .NET Core On Liunx环境搭建之MongoDB
    canvas图像处理汇总
    mysql数据库高并发处理
    nginx 重发机制导致的重复扣款问题
  • 原文地址:https://www.cnblogs.com/-citywall123/p/12846941.html
Copyright © 2011-2022 走看看