当前位置: 首页 > news >正文

使用reserve来避免不必要的内存重新分配

STL容器的内存分配策略是,他们会自动增长以便容纳下你放入其中的数据,只要没有超过它的最大限制就可以(要查看最大限制可调用名为max_size的成员函数)。对于vector和string,增长过程是这样来实现的: 每当需要更多空间时,就调用与realloc类似的操作。这一类似realloc的操作分为如下四个步骤:

1.分配一块大小为当前容量的每个倍数的新内存。在大多数实现中,vector和string的容量每次以2的倍数增长,即每当扩容时,他们的容量加倍。

2.把容器的所有元素从旧的内存复制到新的内存中。

3.析构掉旧内存中的对象。

4释放旧内存。

每当发生内存的重新分配时,会带来以下两个问题:

1.效率问题。一系列的分配、释放、复制和析构步骤,必然会带来程序效率的下降。

2.迭代器失效。原来指向旧的容器的迭代器都将失效,因为那块内存已经被释放掉了。

下来我们介绍reserve成员函数。

reserve成员函数能使你把重新分配内存的次数减少到最低,从而避免了重新分配内存带来的问题。在介绍reserve怎么做到这些之前,先来看一下这四个相互关联、但有时会被混淆的成员函数。在标准容器中,只有vector和string提供了所有者四个函数:

1.size()。size()告诉你容器中有多少个元素。它不会告诉你该容器为自己所包含的元素分配了多少内存。

2.capacity()。capacity()告诉你该容器利用已经分配的内存可以容纳多少元素。这是容器所能容纳的元素总数,而不是它还能容纳多少元素。如果你想知道一个vector有多少未被使用的内存,就需要用capacity()-size()。如果capacity和size返回同样的值,就说明容器中不再有剩余空间了,因此下一个插入操作将导致上面讲过的重新分配过程。

3.resize(Container::size_type n)。resize()强迫容器把它的容量改变到包含n个元素的状态。在调用resize之后,size将返回n。如果n比当前的大小(size)要小,则容器尾部的元素将会被析构。如果n比当前的大小要大,则通过默认的构造函数创建的新元素将被添加到容器的尾部。如果n比当前的容量(capacity)要大,那么在添加元素之前,将先重新分配内存。

4.reserve(Container::size_type n)。reserve强迫容器把它的容量变为至少是n,前提是n不小于当前的大小。这通常会导致内存重新分配,因为容量需要增加。如果n比当前容量要小,则vector忽略该调用,什么也不做;而string则可能把自己的容量减为size和n中的最大值。

通过以上概括,应该清楚的是:当一个元素需要被插入容器而容量不够时,就会发生重新分配过程(包括原始内存的分配和释放,对象的拷贝和析构,迭代器、指针和引用的失效)。因此,避免重新分配的关键在于,今早地使用reserve,把容量设置为足够大的值,最好是在容器刚被构造出来之后就使用reserve。

例如,要创建一个包含1-1000之间的值得vector<int>。如果不使用reserve,可能这么写

vector<int> v;
for(int i=1;i<=1000;i++){
    v.push_back(i);
}

对于大多数STL实现,该循环在执行过程中将会导致2到10次重新分配(2的十次方)。把这段改代码改为使用reserve,如下所示 

vector<int> v;
v.reserve(1000);
for(int i=1;i<=1000;i++){
    v.push_back(i);
}

则在循环过程中,就不会发生内存重新分配。

大小(size)和容量(capacity)之间的关系使我们能够预知什么时候插入操作会导致vector或string执行重新分配的动作,进而又使我们有可能预知下一个插入操作什么时候回是容器中的迭代器、指针、和引用失效。例如,考虑下面的代码

string s;
if(s.size() < s.capacity()){
    s.push_back('x');
}

 对push_back的调用不会使string中的迭代器、指针和引用失效,因为string的容量肯定大于它的大小。

总结:通常有两种方式来使用reserve以避免不必要的内存重新分配。第一种方式是,若能确切知道或大致预计容器中最终会有多少元素,则此时可使用reserve。第二种方式是,先预留足够大的空间,然后,当把所有的数据都加入后,再去除多余的容量。如何去除多余的容量,这里有个诀窍,就是使用swap技巧。关于swap技巧的说明,以后会再更新一篇博文专门讲解。

 

参考:《Effective STL》 第14条

 

相关文章:

  • redis 编译报错 zmalloc.h:50:10: fatal error: jemalloc/jemalloc.h: 没有那个文件或目录
  • linux下hiredis安装、C接口编程
  • redis源码学习之数据结构---双向链表
  • redis源码分析--事件驱动模型
  • ubuntu下zmq编译安装及请求-应答模式测试
  • c++输出:怎么解决数字过大时默认使用科学计数法输出的问题?
  • c++11实现一个自动注册的工厂模式
  • zmq发布-订阅模式c++实现
  • linux报错:bash: syntax error near unexpected token `(‘ --路径中有括号怎么处理?
  • golang学习总结--函数
  • golang学习总结--结构体、接口
  • 解决运行时报错:error while loading shared libraries xxx.so,cannot open shared object file
  • 超实用:linux shell光标移动常用快捷键
  • git commit之后如何撤销
  • golang学习总结--协程、channel
  • (三)从jvm层面了解线程的启动和停止
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • java2019面试题北京
  • Javascript设计模式学习之Observer(观察者)模式
  • Java的Interrupt与线程中断
  • Java读取Properties文件的六种方法
  • java小心机(3)| 浅析finalize()
  • mockjs让前端开发独立于后端
  • Python_网络编程
  • Sequelize 中文文档 v4 - Getting started - 入门
  • Spring Cloud Feign的两种使用姿势
  • TypeScript迭代器
  • yii2中session跨域名的问题
  • 复杂数据处理
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • # Maven错误Error executing Maven
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #include<初见C语言之指针(5)>
  • (二)PySpark3:SparkSQL编程
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (转)树状数组
  • **PHP二维数组遍历时同时赋值
  • . Flume面试题
  • . NET自动找可写目录
  • .bat批处理出现中文乱码的情况
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net 获取url的方法
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET学习教程二——.net基础定义+VS常用设置
  • @ComponentScan比较
  • @ConditionalOnProperty注解使用说明
  • @javax.ws.rs Webservice注解
  • @RequestBody的使用
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku