Rust語言中的String和HashMap使用示例詳解
String
字符串是比很多開發(fā)者所理解的更為復雜的數(shù)據(jù)結(jié)構(gòu)。加上 UTF-8 的不定長編碼等原因,Rust 中的字符串并不如其它語言中那么好理解。
Rust 的核心語言中只有一種字符串類型:str。字符串 slice,它通常以被借用的形式出現(xiàn):&str 是一些儲存在別處的 UTF-8 編碼字符串數(shù)據(jù)的引用。而 String 的類型是由標準庫提供的,而沒有寫進核心語言部分,它是可增長的、可變的、有所有權(quán)的、UTF-8 編碼的字符串類型。
?? Rust 標準庫中還包含一系列其他字符串類型,比如 OsString、OsStr、CString 和 CStr。相關(guān)庫 crate 還會提供更多儲存字符串數(shù)據(jù)的數(shù)據(jù)類型。這些字符串類型能夠以不同的編碼,或者內(nèi)存表現(xiàn)形式上以不同的形式,來存儲文本內(nèi)容。
新建字符串
- String::new()函數(shù)
- to_string()方法
let data = "initial contents"; let s = data.to_string(); // 或者 let s = String::from(data); // 該方法也可直接用于字符串字面量: let s = "initial contents".to_string();
更新字符串
String 的大小可以增加,其內(nèi)容也可以改變。另外,還可以使用 + 運算符或 format! 宏來拼接 String 值。
- push_str()
- push()
let mut s = String::from("foo");
let t = String::from("bar");
s.push_str(&t);
// push 方法被定義為獲取一個單獨的字符作為參數(shù),并附加到 String 中
let mut l = String::from("lo");
l.push('l');
?? pub fn push_str(&mut self, string: &str) 方法不會獲得字符串的所有權(quán)。另外值得一提的是,t 是 &String 類型,而 push_str 方法需要的是 &str 類型的參數(shù)。為什么這段代碼能夠正常編譯呢?這里就涉及到了 解引用強制轉(zhuǎn)換(deref coercion),我們將在后面的文章中介紹它。
使用 + 運算符或 format! 宏拼接字符串
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;
你可以把他理解成 C++ 的運算符重載。在 Rust 中, + 的實現(xiàn)可能是 fn add(self, s: &str) -> String 這樣一個方法。
?? s1 的所有權(quán)將被移動到 add 調(diào)用中
如果想要級聯(lián)多個字符串,使用 + 就變得麻煩了。這時候可以使用 format! 宏:
let s1 = String::from("hello");
let s2 = String::from("the");
let s3 = String::from("world");
let s = format!("{}-{}-{}", s1, s2, s3);
索引字符串
在其他語言中,通過索引來引用字符串中的某個單獨字符是很常見的操作。但在 Rust 中,你可能會遇到問題:

這主要是因為:
- UTF-8 是不定長編碼,而 String 的實現(xiàn)是基于
Vec<u8>的封裝:數(shù)組中每一個元素都是一個字節(jié),但 UTF-8 中每一個漢字(或字符)都可能由一到四個字節(jié)組成 - 索引操作預期總是需要常數(shù)時間 (O(1))。但是對于
String不可能保證這樣的性能,因為 Rust 必須從開頭到索引位置遍歷來確定有多少有效的字符。
字符串 slice
如果你真的希望使用索引創(chuàng)建字符串 slice 時,Rust 會要求你明確字符串范圍。這時你需要一個字符串 slice,使用 [] 和一個 range 來創(chuàng)建含特定字節(jié)的字符串 slice:
fn main() {
let s1 = String::from("你好,");
println!("{}", &s1[0..3]); // 你
}
如果獲取 &s1[0..1] ,Rust 在運行時會 panic。因此,你應該謹慎地使用這個操作,因為這么做可能會使你的程序崩潰。
遍歷字符串
可以使用 chars() 方法獲取該字符串的字母數(shù)組。
fn main() {
let s1 = String::from("你好,");
let s2 = String::from("世界!");
let s3 = s1 + &s2;
for char in s3.chars() {
println!("{}", char);
}
}
HashMap
另外一個常用集合類型是 哈希 map(hash map)。HashMap<K, V> 類型儲存了一個鍵類型 K 對應一個值類型 V 的映射。它通過一個 哈希函數(shù)(hashing function)來實現(xiàn)映射,決定如何將鍵和值放入內(nèi)存中。
哈希 map 適用于需要任何類型作為鍵來尋找數(shù)據(jù)的情況,而不是像 vector 那樣通過索引。
新建 HashMap
使用new 創(chuàng)建一個空的 HashMap,并使用 insert 增加元素:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
?? 必須首先 use 標準庫中集合部分的 HashMap。在這上面介紹的三個常用集合中,HashMap 是最不常用的,所以并沒有被 prelude 自動引用。標準庫中對 HashMap 的支持也相對較少,例如,并沒有內(nèi)建的構(gòu)建宏。
?? 像 vector 一樣,哈希 map 將它們的數(shù)據(jù)儲存在堆上;哈希 map 是同質(zhì)的:所有的鍵必須是相同類型,值也必須都是相同類型。
另一個構(gòu)建哈希 map 的方法是使用一個元組的 vector 的 collect 方法:
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
HashMap 和 ownership
- 對于像
i32這樣的實現(xiàn)了Copytrait 的類型,其值可以拷貝進哈希 map。 - 對于像
String這樣擁有所有權(quán)的值,其值將被移動而哈希 map 會成為這些值的所有者。
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 此時 field_name 和 field_value 被移動到了 map 中
?? 如果將值的引用插入哈希 map,這些值本身將不會被移動進哈希 map。但是這些引用指向的值必須至少在哈希 map 有效時也是有效的。此時就涉及到生命周期的內(nèi)容。
訪問 HashMap 中的值
可以通過 get 方法并提供對應的鍵來從哈希 map 中獲取值:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
get 返回 Option<V>,所以結(jié)果被裝進 Some;如果某個鍵在哈希 map 中沒有對應的值,get 會返回 None。當獲取到結(jié)果后,就需要使用到 match 進行匹配。
更新 HashMap
在更新前,我們需要考慮以下幾種情況:
- 已有
key-value,直接覆蓋 - 只在沒有
key-value時插入 - 利用已有
key-value來更新
直接覆蓋
insert() 方法:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
新插入
利用 entry() 函數(shù)返回的枚舉值,調(diào)用 or_insert() 方法進行處理:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
?? Entry 的 or_insert 方法在鍵對應的值存在時就返回這個值的可變引用,如果不存在則將參數(shù)作為新值插入并返回新值的可變引用。
更新舊值
or_insert 方法事實上會返回這個鍵的值的一個可變引用(&mut V):
// 統(tǒng)計字符串中某個單詞的出現(xiàn)次數(shù)
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0); // 之前不存在對應關(guān)系就初始化并置計數(shù)器為0
*count += 1; // 每次計數(shù)器加一
}
println!("{:?}", map);
?? 這里我們將這個可變引用儲存在 count 變量中,所以為了賦值必須首先使用星號( * )解引用 count。這個可變引用在 for 循環(huán)的結(jié)尾離開作用域,這樣所有這些改變都是安全的并符合借用規(guī)則。
總結(jié)
掌握了 Rust 中最常用的三種集合類型,現(xiàn)在你已經(jīng)可以開始進行一些包含復雜邏輯的編程了!在此過程中你可能會遇到很多錯誤。因此接下來我將介紹錯誤處理與模式匹配,并開始介紹一些測試工具和自動化測試的內(nèi)容,更多關(guān)于Rust String HashMap使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解讀Rust的Rc<T>:實現(xiàn)多所有權(quán)的智能指針方式
Rc<T> 是 Rust 中用于多所有權(quán)的引用計數(shù)類型,通過增加引用計數(shù)來管理共享數(shù)據(jù),只有當最后一個引用離開作用域時,數(shù)據(jù)才會被釋放,Rc<T> 適用于單線程環(huán)境,并且只允許不可變共享數(shù)據(jù);需要可變共享時應考慮使用 RefCell<T> 或其他解決方案2025-02-02
Rust并發(fā)編程之使用消息傳遞進行線程間數(shù)據(jù)共享方式
文章介紹了Rust中的通道(channel)概念,包括通道的基本概念、創(chuàng)建并使用通道、通道與所有權(quán)、發(fā)送多個消息以及多發(fā)送端,通道提供了一種線程間安全的通信機制,通過所有權(quán)規(guī)則確保數(shù)據(jù)安全,并且支持多生產(chǎn)者單消費者架構(gòu)2025-02-02
Rust?實現(xiàn)?async/await的詳細代碼
異步編程在 Rust 中的地位非常高,很多 crate 尤其是多IO操作的都使用了 async/await,這篇文章主要介紹了Rust?如何實現(xiàn)?async/await,需要的朋友可以參考下2022-09-09
rust中間件actix_web在項目中的使用實戰(zhàn)
這篇文章主要介紹了rust中間件在項目中的使用實戰(zhàn),包括自定義中間件,日志中間件,Default?headers,用戶會話,錯誤處理的用法實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01

