8.5.0. 本章內容
第八章主要講的是Rust中常見的集合。Rust中提供了很多集合類型的數據結構,這些集合可以包含很多值。但是第八章所講的集合與數組和元組有所不同。
第八章中的集合是存儲在堆內存上而非棧內存上的,這也意味著這些集合的數據大小無需在編譯時就確定,在運行時它們可以動態地變大或變小。
本章主要會講三種集合:Vector、String和HashMap(本文)
喜歡的話別忘了點贊、收藏加關注哦(加關注即可閱讀全文),對接下來的教程有興趣的可以關注專欄。謝謝喵!(=・ω・=)
8.5.1. 什么是HashMap
HashMap的形式是HashMap<K,V>
,其中K
代表鍵(key),V
代表值(value)。HashMap以鍵值對的形式存儲數據,一個鍵對應一個值。很多語言都支持這樣的集合數據結構,但是不一定是這個叫法,比如說C#中相同概念的數據結構叫字典(dictionary)。
HashMap的內部實現使用了Hash函數,中文叫哈希函數,這個函數決定了如何在內存中存儲鍵與值。
在Vector
中我們使用索引來訪問數據,但有的時候你想要的是通過鍵(鍵可以是任何數據類型)來尋找數據,而不是通過索引(或者說你不清楚這個數據在哪個索引)。這種情況就可以使用HashMap。
需要注意的是,HashMap是同構的,也就是說在一個HashMap中,所有的鍵必須是同一類型,所有的值必須是同一類型。
8.5.2. 創建HashMap
- 由于HashMap不常用,所以Rust并沒有把它集成到預導入模塊(Prelude),使用前需要引入HashMap,在代碼開頭寫上:
use std::collections::HashMap;
- 創建空的HashMap使用
Hash::new()
函數 - 添加數據使用
insert()
方法
看個例子:
use std::collections::HashMap; fn main() { let mut scores:HashMap<String, i32> = HashMap::new();
}
在這里創建了一個名為scores
的變量來存儲HashMap,由于Rust是強語言類型,它必須知道你在HashMap里存儲什么數據類型。又因為沒有前后文可供編譯器推斷,所以在聲明時就必須把HashMap里鍵和值的數據類型顯式聲明出來,在代碼中就是scores
的鍵被設為了String
類型,值被設為了i32
類型。
當然,如果你在后文給這個HashMap上添加了數據,Rust就會根據添加的數據類型自動推斷鍵和值的數據類型。添加數據使用insert()
方法。例子如下:
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); scores.insert(String::from("dev1ce"), 0);
}
因為在第5行往scores
里添加了鍵值對,且鍵String::from("dev1ce")
是String
類型,值0是i32
(Rust默認整數是i32
)類型,所以編譯器就會自己推斷出scores
是一個HashMap<String, i32>
類型的HashMap,因此第四行在聲明時就不需要顯式聲明了。
8.5.3. 將兩個Vector合為一個HashMap
在元素類型為元組的Vector
上使用collect
方法,可以使用HashMap。換個說法,假如你有兩個Vector
,這兩個Vector
上的所有值都有一一對應關系,這個時候就可以使用collect
方法,把一個Vector
里的數據作為鍵,另一個作為值,放到HashMap里。如下例:
use std::collections::HashMap; fn main() { let player = vec![String::from("dev1ce"), String::from("Zywoo")]; let initial_scores = vec![0, 100]; let scores: HashMap<_, _> = player.iter().zip(initial_scores.iter()).collect();
}
player
這個Vector
是用來存儲選手名字的,里面的元素是String
類型initial_scores
這個Vector
是用來存儲每個選手對應的得分的player.iter()
和initial_scores.iter()
是兩個Vector
的遍歷器,使用.zip()
方法就可以創建一個元組的數組,player.iter().zip(initial_scores.iter())
就是創建一個player
中的元素在前,initial_scores
中的元素在后的元組數組,想換元素位置就可以把代碼中的兩個迭代器呼喚位置即可。然后再使用.collect()
方法來把元組轉化為HashMap。- 最后要注意的一點是
.collect()
它支持轉換為很多數據結構,如果聲明時不顯式聲明其類型程序就會報錯,這里就指明了類型是HashMap<_, _>
,<>
中的兩個數據類型編譯器可以根據代碼(也就是找兩個的Vector
的數據類型)來推斷,所以這里可以寫_
占位符讓它自行推斷。
8.5.4. HashMap和所有權
對于實現了Copy trait的數據類型(例如i32
在內的絕大多數簡單數據類型),值會被復制到HashMap中,原先的變量仍然可用。對于沒有實現的(例如String
),所有權會被交給HashMap。
如果將值的引用插入到HashMap,值本身就不會移動。在HashMap的有效期間,被引用的值必須保持有效。
8.5.5. 訪問HashMap中的值
訪問值可以使用get
方法。get
方法的參數是HashMap的鍵,返回值是Option<&V>
這個枚舉。看個例子:
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); scores.insert(String::from("dev1ce"), 0); scores.insert(String::from("Zywoo"), 100);let player_name = String::from("dev1ce"); let score = scores.get(&player_name); match score { Some(score) => println!("{}", score), None => println!("Player not found"), };
}
- 首先創建了一個空的
HashMap
叫做scores
,然后又通過insert
方法往里面添加了兩個鍵值對(“dev1ce”, 0)和(“Zywoo”, 100),鍵類型是String
,值類型是i32
。 - 然后又聲明了名為
player_name
的String
變量,其值為"dev1ce"。 - 接著就通過HashMap上的
get
方法在scores
找player_name
(&
表示引用)這個鍵所對應的值,但是get
方法返回的是Option枚舉,所以這里先把這個Option枚舉值賦給score
后面再來解包。 - 最后使用了
match
表達式來處理score
,如果找到了對應的值,score
這個枚舉類型就會是變體Some
,把Some
所關聯的值綁定在score
上,然后再打印出來。如果找不到,score
這個枚舉類型就會是變體None
,這個時候就會打印"Player not found"。
輸出:
0
8.5.6. 遍歷HashMap
遍歷HashMap一般使用for
循環。如下例:
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); scores.insert(String::from("dev1ce"), 0); scores.insert(String::from("Zywoo"), 100); for (k, v) in &scores { println!("{}: {}", k, v); }
}
這個for
循環使用的是HashMap的引用,也就是&scores
,因為通常遍歷之后還要繼續使用這個HashMap,所以使用引用就不會失去所有權,前面的(k,v)
是一個模式匹配,第一個值就是鍵,這里賦給了k
;第二個是值,這里賦給了v
。
輸出:
Zywoo: 100
dev1ce: 0