原文标题:Why doesn't Rust's BTreeMap have a with_capacity() method?


原文链接:https://www.nicolas-hahn.com/2020/11/30/btreemap-with-capacity/

公众号: Rust 碎碎念


翻译 by: Praying

声明:我发现这里已经有一篇解释,但是我认为它有点不太好理解,所以我希望我写的这篇文章能够更容易理解一些。

Rust 的 HashMap(以及 HashSet 和 Vec)集合都提供了一个初始化方法—— fn with_capacity(capacity: usize),该方法预先分配足够的内存空间以存储capacity个元素。为什么 BTreeMap(和 BTreeSet)没有这个方法呢?

答案就在于这两个结构体在内存中布局的不同。简而言之,HashMap,就像 Vec,使用了一个 array(一个连续的内存块),要求在 O(1)的时间内插通过索引插入和查找元素。在 Vec 中,这很明显,但是在 HashMap 中,key 是被 hash 之后转为 value 在数组中的索引。

让我们来看一个已经存入四条记录的 HashMap(简单起见,我打算忽略真实的实现细节,比如 hash 碰撞时的装桶(bucket))。它在本质上来讲是一个拥有四个元素的数组。下面是一个表示存有三条记录的 HashMap 的内存表示(每个格子为一个字节),以及若干个方格(亮绿色是内存中被填充的字节,深绿色是空的,但是被结构体保留)。

我们插入两个元素。现在我们需要分类更多内存以存放第五个元素。常见的实现是将数组的大小翻倍(以便于我们不必在每次插入时都进行分配)。在理想情况下,我们可以直接使用内存中接下来的四个字节。

(事实上,元素是不可能像这样被连续存放的,因为 hasher 会以近似随机分布的方式输入一个数组的索引)。

尽管如此,如果接下来的四个字节已经被分配给其他的结构体了会怎么样呢?


在这种情况下,我们需要把整个 HashMap 移动到内存中的某个可以容下八条记录的位置。不同于额外分配四个字节 ,这次我们需要先分配八个字节(将数据拷贝过去),然后析构原来的四个字节,这个开销就比较高了。


这里就是with_capacity()出现的原因。如果我们预先知道我们至少会有五个元素,那么预先分配八个字节就能让我们不必反复析构和重分配,这也是with_capacity()所做的事情。

那么 BTreeMap 为什么没有这个方法呢?来看一下BTree 是如何工作的。在下面这个例子中,我打算把它简化为一个普通的二分查找树。它们俩之间的本质区别在于,BST(二分查找树)的每个节点有一个值和两个指针,但是一个 BTree 的每个节点拥有一组值和一组指针:

这里为了便于上面的解释,它们暂时可以被视作等同。

BST 的每个节点由一个值和两个分别指向左右子节点的指针组成。下面是一个只有一个节点和值的BTreeMap(亮蓝色)。第二个和第三个暗蓝色的字节被保留用于指向子节点的指针,目前是空的。

当一个元素被插入时,一个新节点会被创建并且会分配属于它的内存。因为指针可以指向内存中的任意地址,所以不必要求节点像 HashMap 那样在内存中存储为连续的字节。如果我们打算插入一条新记录,会如下图所示:


我们可以把这条新记录放在内存中任意拥有三个字节的自由空间的位置。一个 BTreeMap 可以遍布在程序的内存各处,因为我们不必把记录连续存放。这意味着,我们将从不需要析构和重分配空间以拷贝记录(元素),所以我们不会在 BTreeMap 初始化时通过预先分配额外的内存空间来节省某些环节(在整个程序运行时)。

如果你明确想要预先分配以节省插入过程的时间,或者如果这时的延迟代价很大, BTreeMap::with_capacity()或许会有意义。但我想这种用例对于标准库函数而言过于特殊。在有用(usefulness)和臃肿之间存在一个微妙的平衡。

欢迎关注公众号:Rust碎碎念,获取更多好文章

【译】为什么Rust中的BTreeMap没有with_capacity()方法?的更多相关文章

  1. 【译】Rust中的array、vector和slice

    原文链接:https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/ 原文标题:Arrays, vectors and slices in ...

  2. Rust中的结构体及方法语法

    这个可以和类作比较,或是go当中的方法比较. #[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle ...

  3. 【译】理解Rust中的闭包

    原文标题:Understanding Closures in Rust 原文链接:https://medium.com/swlh/understanding-closures-in-rust-21f2 ...

  4. 【译】理解Rust中的局部移动

    原文标题:Understanding Partial Moves in Rust 原文链接:https://whileydave.com/2020/11/30/understanding-partia ...

  5. 【译】理解Rust中的Futures (一)

    原文标题:Understanding Futures In Rust -- Part 1 原文链接:https://www.viget.com/articles/understanding-futur ...

  6. 【译】理解Rust中的Futures(二)

    原文标题:Understanding Futures in Rust -- Part 2 原文链接:https://www.viget.com/articles/understanding-futur ...

  7. 【译】深入理解Rust中的生命周期

    原文标题:Understanding Rust Lifetimes 原文链接:https://medium.com/nearprotocol/understanding-rust-lifetimes- ...

  8. Rust初步(四):在rust中处理时间

    这个看起来是一个很小的问题,我们如果是在.NET里面的话,很简单地可以直接使用System.DateTime.Now获取到当前时间,还可以进行各种不同的计算或者输出.但是这样一个问题,在rust里面, ...

  9. 译:DOM2中的高级事件处理(转)

    17.2. DOM2中的高级事件处理(Advanced Event Handling with DOM Level 2)        译自:JavaScript: The Definitive Gu ...

随机推荐

  1. 这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

    前言 如果你想深入研究Java并发的话,那么AQS一定是绕不开的一块知识点,Java并发包很多的同步工具类底层都是基于AQS来实现的,比如我们工作中经常用的Lock工具ReentrantLock.栅栏 ...

  2. java的for循环中遇到异常抛出后继续循环执行

    @Testpublic void loopTryCatchTest() throws Exception { Map<String, Object> a = new HashMap(); ...

  3. 转载 数据库优化 - SQL优化

    判断问题SQL判断SQL是否有问题时可以通过两个表象进行判断: 系统级别表象CPU消耗严重IO等待严重页面响应时间过长应用的日志出现超时等错误可以使用sar命令,top命令查看当前系统状态. 也可以通 ...

  4. 01Java环境安装监测

    下载安装JDK JDK:Java开发套件 JDK下载 监测JDK安装是否成功 运行Java命令 运行Javac命令

  5. 利用Vmware workstation安装MS-DOS使用Turbo C 2.0

    首先上各种软件的链接 链接:https://pan.baidu.com/s/1HfTDexiJSpioUnzKddUIyA 提取码:khtu 这个链接是压缩后的 链接:https://pan.baid ...

  6. property,类方法和静态方法

    # from math import pi # # class Circle: # def __init__(self, r): # self.r = r # # @property # def pe ...

  7. 在Windows下使用CodeBlock使用libiconv第三方库

    在Windows下使用CodeBlock使用libiconv第三方库 1.         选择在Project->Build options下: 2.         如下图添加libicon ...

  8. 4 种高可用 RocketMQ 集群搭建方案!

    背景 笔者所在的业务线,最初化分为三个服务,由于业务初期业务复杂度相对简单,三个业务服务都能很好的独立完成业务功能. 随着产品迭代,业务功能越来越多后慢慢也要面对高并发.业务解耦.分布式事务等问题,所 ...

  9. SQL注入步骤

    1.判断是否存在注入,注入是字符型还是数字型2.猜解SQL查询语句中的字段数3.确定回显的字段数4.获取当前数据库5.获取表中字段名6.下载数据

  10. JDK 15已发布,你所要知道的都在这里!

    JDK 15已经在2020年9月15日发布!详情见 JDK 15 官方计划.下面是对 JDK 15 所有新特性的详细解析! 官方计划 2019/12/12 Rampdown Phase One (fo ...