一、引例
1、string 扩容概述
- string 就是动态字符数组,一旦出现 ‘动态’ 二字,就不可能一开始就申请很大的内存,一定有它内部的申请策略;
- vector 的动态扩容策略可以参考我之前写的一篇博客:
C++ vector 扩容策略.
2、扩容尝试
- 通过 VS2013 环境下,string 在一个一个插入元素 (push_back)的过程中,size 和 capacity 的对应关系如下;
size | capacity |
---|
0 | 15 |
1 | 15 |
… | 15 |
14 | 15 |
15 | 15 |
16 | 31 |
17 | 31 |
… | 31 |
30 | 31 |
31 | 31 |
32 | 47 |
33 | 47 |
- 观察发现,只要 string 这个对象创建出来了,无论有没有元素,就占用了15个字节,当 size 达到 capacity 时,又增加了 16 个字节;
二、扩容逻辑猜测
- 声明:因为我的环境上没有找到 string 的源码, push_back 的断点也没法跟进去,所以只能猜了;
1、猜测一:常数增量
15 -> 31 -> 47
- 猜测 string 的增长策略为每次 capacity 不足时,采取增加 16 个字节的方案;
- 因为数据太少没有说服力,所以我们准备更多数据,再来看看情况如何:
size | capacity | capacity - oldcapacity |
---|
0 | 15 | 0 |
16 | 31 | 16 |
32 | 47 | 16 |
48 | 70 | 23 |
71 | 105 | 35 |
106 | 157 | 52 |
158 | 235 | 78 |
236 | 352 | 117 |
353 | 528 | 176 |
529 | 792 | 264 |
793 | 1188 | 396 |
1189 | 1782 | 594 |
1783 | 2673 | 891 |
2674 | 4009 | 1336 |
4010 | 6013 | 2004 |
6014 | 9019 | 3006 |
9020 | 13528 | 4509 |
- 当某次 size > capacity 时,增长的 capacity 增量并不是一个常数,所以 猜测一 被推翻;
2、猜测二:倍数增量
- 猜测是倍增的策略以后,我们就要看这个倍增因子了,将每次 size > capacity 时,两次 capacity 相除,得到如下的表:
size | capacity | capacity / oldcapacity |
---|
0 | 15 | / |
16 | 31 | 2.066667 |
32 | 47 | 1.516129 |
48 | 70 | 1.489362 |
71 | 105 | 1.500000 |
106 | 157 | 1.495238 |
158 | 235 | 1.496815 |
236 | 352 | 1.497872 |
353 | 528 | 1.500000 |
529 | 792 | 1.500000 |
793 | 1188 | 1.500000 |
1189 | 1782 | 1.500000 |
1783 | 2673 | 1.500000 |
2674 | 4009 | 1.499813 |
4010 | 6013 | 1.499875 |
6014 | 9019 | 1.499917 |
9020 | 13528 | 1.499945 |
- 我们发现,capacity 大于 47 以后,都是呈 1.5 的倍率在增长的,不过为什么会有精度误差呢?
- 继续输出一些信息得知:
size | capacity | capacity / oldcapacity | oldcapacity + oldcapacity / 2 |
---|
0 | 15 | / | / |
16 | 31 | 2.066667 | 22 |
32 | 47 | 1.516129 | 46 |
48 | 70 | 1.489362 | 70 |
71 | 105 | 1.500000 | 105 |
106 | 157 | 1.495238 | 157 |
158 | 235 | 1.496815 | 235 |
236 | 352 | 1.497872 | 352 |
353 | 528 | 1.500000 | 528 |
529 | 792 | 1.500000 | 792 |
793 | 1188 | 1.500000 | 1188 |
1189 | 1782 | 1.500000 | 1782 |
1783 | 2673 | 1.500000 | 2673 |
2674 | 4009 | 1.499813 | 4009 |
4010 | 6013 | 1.499875 | 6013 |
6014 | 9019 | 1.499917 | 9019 |
9020 | 13528 | 1.499945 | 13528 |
- 于是我们发现,capacity 大于 47 以后,capacity = oldcapacity + oldcapacity / 2;
- 当 oldcapacity 为偶数的时候,正好 1.5 倍;当 oldcapacity 为奇数的时候,每次除 2 在 c++ 中都是取下整的,所以才会有 1.5 倍倍增的误差;
三、扩容逻辑实现
- 根据以上的实验得知,string 的扩容策略如下:
size_type _Grow_to(size_type _Count) const
{
size_type _Capacity = capacity();
if ( _Capacity < 32 ) {
_Capacity = _Capacity + 16;
}else {
_Capacity = max_size() - _Capacity / 2 < _Capacity
? 0 : _Capacity + _Capacity / 2;
}
if (_Capacity < _Count)
_Capacity = _Count;
return (_Capacity);
}