面试题:c++vector的动态扩容,为何是1.5倍或者是2倍(代码片段)

森明帮大于黑虎帮 森明帮大于黑虎帮     2023-01-09     643

关键词:

一、概述

在面试时vector的扩容问题会经常被问到,比如:

  • vector是如何进行扩容的?
  • 扩容会导致效率低下,那如何避免动态扩容呢?
  • 为什么选择以1.5倍或者2倍方式进行扩容?而不是3倍4倍扩容?
  • vs为什么选择1.5倍,linux为什么选择2倍?

一系列问题下来,是否有种被吊打的感觉呢?本节我们再来深究vector扩容背后所包含的细节问题,让你的面试不再尴尬。

二、 高效使用vector,避免扩容

1.扩容机制回顾

当向vector中插入元素时,如果元素有效个数size与空间容量capacity相等时,vector内部会触发扩容机制:

开辟新空间

  • 拷贝元素。
  • 释放旧空间。

注意:每次扩容新空间不能太大,也不能太小,太大容易造成空间浪费,太小则会导致频繁扩容而影响程序效率。

2.如何避免扩容导致效率低

如果要避免扩容而导致程序效率过低问题,其实非常简单:如果在插入之前,可以预估vector存储元素的个数,提前将底层容量开辟好即可。如果插入之前进行reserve,只要空间给足,则插入时不会扩容,如果没有reserve,则会边插入边扩容,效率极其低下。

#include <iostream>
#include <vector>
int main ()

    size_t sz;
    std::vector<int> foo;
    // foo.reserve(100);   // 先将vector底层空间扩增到100个,然后插入
    sz = foo.capacity();
    std::cout << "making foo grow:\\n";
    for (int i=0; i<100; ++i)
    
        foo.push_back(i);
        if (sz!=foo.capacity())
        
            sz = foo.capacity();
            std::cout << "capacity changed: " << sz << '\\n';
        




vs:运行结果:
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141

g++运行结果:
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128

三、为什么选择以倍数方式扩容

1. 以等长个数进行扩容

等长个数方式扩容,新空间大小就是将原空间大小扩增到capacity+K个空间(capacity为旧空间大小)。假设需要向vector中插入100个元素,K为10,那么就需要扩容10次;每次扩容都需要将旧空间元素搬移到新空间,第i次扩容拷贝的元素数量为:ki(第1次扩容,新空间大小为20,旧空间中有10个元素,需要搬移到新空间中;第2次扩容,新空间大小为30,旧空间中有20个元素,需要全部搬移到新空间中),假设元素插入与元素搬移为1个单位操作,则n个元素push_back()的总操作次数为:

2. 以倍数方式进行扩容

假设有n个元素需要像vector插入,倍增因子为m,则完成n个元素像vector的push_back操作需要扩容log以m为低n的次方。比如:以二倍方式扩容,当向vector插入1000个元素,需要扩容log以2为底1000次方,就是扩容10次,第i次增容会把m的i次方个元素搬移到新空间,n次push_back的总操作次数为:

可以看到以倍数的方式扩容比以等长个数的扩容方式效率高。

3. 为什么选择1.5倍或者2倍方式扩容,而不是3倍、4倍

扩容原理为:申请新空间,拷贝元素,释放旧空间,理想的分配方案是在第N次扩容时如果能复用之前N-1次释放的空间就太好了,如果按照2倍方式扩容,第i次扩容空间大小如下:

可以看到,每次扩容时,前面释放的空间都不能使用。比如:第4次扩容时,前2次空间已经释放,第3次空间还没有释放(开辟新空间、拷贝元素、释放旧空间),即前面释放的空间只有1 + 2 = 3,假设第3次空间已经释放才只有1+2+4=7,而第四次需要8个空间,因此无法使用之前已释放的空间,但是按照小于2倍方式扩容,多次扩容之后就可以复用之前释放的空间了。如果倍数超过2倍(包含2倍)方式扩容会存在:

  • 空间浪费可能会比较高,比如:扩容后申请了64个空间,但只存了33个元素,有接近一半的空间没有使用。

  • 无法使用到前面已释放的内存。


使用2倍(k=2)扩容机制扩容时,每次扩容后的新内存大小必定大于前面的总和。
而使用1.5倍(k=1.5)扩容时,在几次扩展以后,可以重用之前的内存空间了。

因为STL标准并没有严格说明需要按何种方式进行扩容,因此不同的实现厂商都是按照自己的方式扩容的,即:linux下是按照2倍的方式扩容的,而vs下是按照1.5倍的方式扩容的。

四、Windows和Linux的扩容底层原理

1.Windows扩容底层

Windows中堆管理系统会对释放的堆块进行合并,因此:vs下的vector扩容机制选择使用1.5倍的方式扩容,这样多次扩容之后,就可以使用之前已经释放的空间。

2.Linux的扩容底层

  • linux下主要使用glibc的ptmalloc来进行用户空间申请的.如果malloc的空间小于128KB,其内部通过brk()来扩张,如果大于128KB且arena中没有足够的空间时,通过mmap将内存映射到进程地址空间.
  • linux中引入伙伴系统为内核提供了一种用于分配一下连续的页而建立的一种高效的分配策略,对固定分区和动态分区方式的折中,固定分区存在内部碎片,动态分区存在外部碎片,而且动态分区回收时的合并以及分配时的切片是比较耗时的.
  • 伙伴系统是将整个内存区域构建成基本大小basicSize的1倍、2倍、4倍、8倍、16倍等,即要求内存空间分区链均对应2的整次幂倍大小的空间,整齐划一,有规律的而不是乱糟糟的。
  • 在分配和释放空间时,可以通过log2(request/basicSize)向上取整的哈希算法快速找到对应内存块。
  • 通过伙伴系统管理空闲分区的了解,可以看到在伙伴系统中的每条空闲分区链中挂的都是2i的页面大小,通过哈希思想进行空间分配与合并,非常高效。估计可能是这个原因SGI-STL选择以2倍方式进行扩容。

五、总结

  • vector在push_back以成倍增长可以在均摊后达到O(1)的事件复杂度,相对于增长指定大小的O(n)时间复杂度更好。
  • 为了防止申请内存的浪费,现在使用较多的有2倍与1.5倍的增长方式,而1.5倍的增长方式可以更好的实现对内存的重复利用。

arraylist相关面试题(代码片段)

ArrayList线程不安全,线程安全的列表有Vector,Collections.synchronizedList(),CopyOnWriteArrayList,线程不安全的list有:ArrayList、LinkedList。ArrayList扩容倍数是1.5倍。intnewCapacity=oldCapacity+(oldCapaci 查看详情

剑指offer:面试题09.用两个栈实现队列(代码片段)

1.要点使用java的同学请注意,如果你使用Stack的方式来做这道题,会造成速度较慢;原因的话是Stack继承了Vector接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。可以使用LinkedList来做Stack的容器,因为... 查看详情

javase面试题——基于jdk1.8中vector的实现原理(源码剖析)(代码片段)

文章目录:1.Vector中的属性2.Vector中的方法2.1构造方法2.2grow方法2.3其他方法1.Vector中的属性Vector中的属性其实跟ArrayList时差不多的,就比ArrayList多了一个protectedintcapacityIncrement; 这个属性是在扩容的时候用到的,它表示... 查看详情

常见arraylist面试题(代码片段)

文章目录ArrayList数组扩容效率问题底层和源码分析扩容添加流程有参构造如何复制某个ArrayList到另一个ArrayList中去?线程安全问题ArrayList插入或删除元素一定比LinkedList慢么ArrayList适合做队列吗ArrayList的遍历和LinkedList遍历性... 查看详情

2021-05-14redis面试题redis持久化数据和缓存怎么做扩容?

Redis持久化数据和缓存怎么做扩容?1、如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。2、如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。... 查看详情

stl面试题

一、讲讲STL的六大组件1、容器:存放数据的各种数据结构2、迭代器:为了访问容器中的元素,是一种泛型指针3、算法:可以操作容器中的元素,如sort、search、copy4、适配器:容器适配器(stack、queue)、算法适配器(mem_fn)、... 查看详情

vector与arraylist

Vector与ArrayList都是采用数组的方式实现,ArrayList进行扩容时总是扩容为原来的1.5倍,Vector中如果increaseCapacitry大于0,则扩容+increaseCapacity.Vector唯一的好处就是线程安全的,但是Java提供了一个工具类Collections,通过该工具类synchronize... 查看详情

80道最新java基础部分面试题

自己整理的面试题,希望可以帮到大家,需要更多资料的可以私信我哦,大家一起学习进步!59、ArrayList和Vector的区别答:这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的... 查看详情

高薪程序员&面试题精讲系列43之hashmap扩容机制的底层实现原理,hashmap扩容后是如何进行rehash操作的?(代码片段)

一.面试题及剖析1.今日面试题请说一下HashMap及其底层实现原理HashMap中是如何计算key的hash值的?HashMap是如何进行扩容的?说说HashMap的扩容机制原理HashMap扩容后是如何重新进行hash计算的?......2.题目剖析在前4篇文章... 查看详情

集合

集合Vector底层结构和ArrayList的比较底层结构版本线程安全(同步)效率扩容倍数ArrayList可变数组jdk1.2不安全,效率高如果有参构造就是1.5倍扩容如果是无参构造1.第一次102.从第二次开始按1.5倍扩Vector可变数组Object[]jdk1.0 查看详情

java面试宝典每日3题:day22

目录1.ArrayList和LinkedList的区别是什么?2.ArrayList和Vector的区别是什么?3.插入数据时,ArrayList、LinkedList、Vector谁速度较快?1.ArrayList和LinkedList的区别是什么?  1.数据结构实现:    ArrayList是动态数... 查看详情

面试高频题难度1.5/5,常规滑动窗口运用题

题目描述这是LeetCode上的2024.考试的最大困扰度,难度为中等。Tag:「滑动窗口」、「双指针」一位老师正在出一场由 道判断题构成的考试,每道题的答案为​​​true​​​(用​​T​​​表示)或者​​false​​​(用​​... 查看详情

java面试题之高级篇研读

1、List和Set比较,各自的子类比较对比一:ArrayList与LinkedList比较  1、ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里连续存放)。  2、因为地址连续,ArrayLi... 查看详情

arraylist与linkedlist常见的问题(代码片段)

...类有哪些,它们分别有什么区别?答:常见的有ArrayList,Vector,LinkedList等。ArrayList底层是数组组成,Vector是线程安全的ArrayList类。LinkedList底层是由链表组成。ArrayList与Vector的区别,扩容上面ArrayList是扩容1.5倍,而Vector是扩容两... 查看详情

80道最新java基础部分面试题

59、ArrayList和Vector的区别答:这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素... 查看详情

java面试题

面试宝典谈一下HashMap的底层原理是什么?谈一下HashMap中put是如何实现的?谈一下HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?谈一下HashMap中get是如何实现的?为什么不直接将key作为哈希值而... 查看详情

java面试题

面试宝典谈一下HashMap的底层原理是什么?谈一下HashMap中put是如何实现的?谈一下HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?谈一下HashMap中get是如何实现的?为什么不直接将key作为哈希值而... 查看详情

2023面试专题:java基础

ArrayList和LinkedList有哪些区别ArrayList扩容机制:ArrayList()会使用长度为零的数组ArrayList(intinitialCapacity)会使用指定容量的数组publicArrayList(Collection<?extendsE>c)会使用c的大小作为数组容量add(Objecto)首次扩容为10,再次扩容为上次... 查看详情