原文标题: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. JLC PCB 嘉立创自动确认生产稿,不讲武德?耗子尾汁!!!

    首先,开局一张图,嘉立创又不做人的一天.嘉立创不讲武德,耗子尾汁!!! 之前下单,勾选了确定生产稿和不加客编,结果生产稿出来还是给我加了客编.那我出10元的意思何在?让我自己花3元看我花的10元有没有 ...

  2. hackrf GPS欺骗

    在对GPS欺骗之前,先对GPS的知识做一个简单的介绍 GPS 系统本身非常复杂, 涉及到卫星通信等各个领域. 这里只是简单介绍一下. 我们通常所说的 GPS 全球定位系统是由美国国防部建造完成. 目前 ...

  3. python-基础入门-6快速入门总结

    快速入门 程序输出: 1.print语句,字符串用双引号括起来 _在解释器中表示最后一个表达式的值. print语句 与字符串格式(%)结合使用,能实现字符串替换功能. >>>pri ...

  4. SpringSecurity之授权

    SpringSecurity之授权 目录 SpringSecurity之授权 1. 写在前面的话 2. web授权 1. 建库 2. 添加查询权限的接口 3. 前端页面的编写 4. SpringSec ...

  5. tcp连接的三次握手

    为什么tcp连接需要三次握手才能建立连接 主要是为了初始化sequence number的初始值,通信的双方要互相通知双方的sequence number,这个要作为以后数据通信的序号,保证以后不会因 ...

  6. C语言讲义——预处理

    C预处理器是一个文本替换工具,在实际编译之前完成一些预先的处理. C预处理器(C Preprocessor)简写为 CPP 预处理器命令都是以#开头,如: #include <stdio.h&g ...

  7. sqli-labs-master 闯关前知识点学习

    1).前期准备.知识点 开始之前,为了方便查看sql注入语句,我在sqli-labs-master网页源码php部分加了两行代码,第一行意思是输出数据库语句,第二行是换行符 一.Mysql 登录 1. ...

  8. CentOS初级扫盲

    发行版介绍 Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX(可移植操作系统接口Portable Operating System Interface ,缩写为 POSIX ...

  9. linux系统下oracle表空间占用情况

    1.我们先查询表空间的占用情况,使用sql如下: select upper(f.tablespace_name) "表空间名", d.tot_grootte_mb "表空 ...

  10. Cassandra与职业发展 | 阿里云栾小凡 &#215; 蔚来汽车张旭东 &#215; 网龙阙乃祯

    # 活动精彩实录 | Cassandra与职业发展 点击此处观看完整活动录像​ 大家好,我叫邓为,我目前在DataStax担任领航架构师.我在DataStax工作了7年多的时间,也有7年多的Cassa ...