【譯】爲何Rust中的BTreeMap沒有with_capacity()方法?

原文標題:Why doesn't Rust's BTreeMap have a with_capacity() method?
原文連接:https://www.nicolas-hahn.com/2020/11/30/btreemap-with-capacity/
公衆號: Rust 碎碎念
翻譯 by: Prayingweb


聲明:我發現這裏已經有一篇解釋,可是我認爲它有點不太好理解,因此我但願我寫的這篇文章可以更容易理解一些。數組


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 的內存表示(每一個格子爲一個字節),以及若干個方格(亮綠色是內存中被填充的字節,深綠色是空的,可是被結構體保留)。佈局

咱們插入兩個元素。如今咱們須要分類更多內存以存放第五個元素。常見的實現是將數組的大小翻倍(以便於咱們沒必要在每次插入時都進行分配)。在理想狀況下,咱們能夠直接使用內存中接下來的四個字節。flex

(事實上,元素是不可能像這樣被連續存放的,由於 hasher 會以近似隨機分佈的方式輸入一個數組的索引)。
spa

儘管如此,若是接下來的四個字節已經被分配給其餘的結構體了會怎麼樣呢?翻譯

在這種狀況下,咱們須要把整個 HashMap 移動到內存中的某個能夠容下八條記錄的位置。不一樣於額外分配四個字節 ,此次咱們須要先分配八個字節(將數據拷貝過去),而後析構原來的四個字節,這個開銷就比較高了。
指針

這裏就是with_capacity()出現的緣由。若是咱們預先知道咱們至少會有五個元素,那麼預先分配八個字節就能讓咱們沒必要反覆析構和重分配,這也是with_capacity()所作的事情。
code

那麼 BTreeMap 爲何沒有這個方法呢?來看一下BTree 是如何工做的。在下面這個例子中,我打算把它簡化爲一個普通的二分查找樹。它們倆之間的本質區別在於,BST(二分查找樹)的每一個節點有一個值和兩個指針,可是一個 BTree 的每一個節點擁有一組值和一組指針:

這裏爲了便於上面的解釋,它們暫時能夠被視做等同。

BST 的每一個節點由一個值和兩個分別指向左右子節點的指針組成。下面是一個只有一個節點和值的BTreeMap(亮藍色)。第二個和第三個暗藍色的字節被保留用於指向子節點的指針,目前是空的。

當一個元素被插入時,一個新節點會被建立而且會分配屬於它的內存。由於指針能夠指向內存中的任意地址,因此沒必要要求節點像 HashMap 那樣在內存中存儲爲連續的字節。若是咱們打算插入一條新記錄,會以下圖所示:

咱們能夠把這條新記錄放在內存中任意擁有三個字節的自由空間的位置。一個 BTreeMap 能夠遍及在程序的內存各處,由於咱們沒必要把記錄連續存放。這意味着,咱們將從不須要析構和重分配空間以拷貝記錄(元素),因此咱們不會在 BTreeMap 初始化時經過預先分配額外的內存空間來節省某些環節(在整個程序運行時)。

若是你明確想要預先分配以節省插入過程的時間,或者若是這時的延遲代價很大, BTreeMap::with_capacity()或許會有意義。但我想這種用例對於標準庫函數而言過於特殊。在有用(usefulness)和臃腫之間存在一個微妙的平衡。

歡迎關注公衆號:Rust碎碎念,獲取更多好文章

相關文章
相關標籤/搜索