前面已經學習了rust的基礎知識,今天我們來學習rust強大的系統庫,從此coding事半功倍。
集合
數組&可變長數組
在 Rust 中,有兩種主要的數組類型:固定長度數組(Fixed-size Arrays)和可變長度數組(Dynamic-size Arrays)。
- 固定長度數組(Fixed-size Arrays):
固定長度數組的長度在編譯時就確定,并且長度不可改變。你可以使用以下語法定義一個固定長度數組:
fn main() {let arr: [i32; 5] = [1, 2, 3, 4, 5];
}
在上面的例子中,我們定義了一個類型為 i32 的固定長度數組 arr
,長度為 5,并初始化了數組的元素。
- 可變長度數組(Dynamic-size Arrays):
Rust 中沒有直接支持可變長度數組的語法。但是,你可以使用Vec<T>
類型來創建一個動態增長的數組。Vec<T>
是一個可變長度的動態數組,可以根據需要動態添加和刪除元素。以下是一個使用Vec<T>
的示例:
fn main() {let mut vec: Vec<i32> = Vec::new();vec.push(1);vec.push(2);vec.push(3);
}
在上面的例子中,我們首先創建了一個空的 Vec<i32>
,然后使用 push
方法向數組中添加元素。 Vec<T>
會根據需要自動調整大小。
需要注意的是,可變長度數組( Vec<T>
)和固定長度數組( [T; N]
)是不同的類型,它們具有不同的性質和用途。下面我們再看下Vec的一些基礎用法:
創建Vec
在 Rust 中,有幾種創建 Vec 的方式:
- 使用 Vec::new() 創建一個空的 Vec:
let mut vec = Vec::new();
這會創建一個長度為 0 的空向量。
- 使用 vec![] 宏創建一個非空的 Vec:
let mut vec = vec![1, 2, 3];
這會創建一個長度為 3、值為 [1, 2, 3] 的可變數組。
- 使用 Vec::with_capacity() 創建一個指定容量的 Vec:
let mut vec = Vec::with_capacity(10);
這會創建一個長度為 0 但預分配了 10 個元素空間的向量。這意味著,在不重新分配內存的情況下,vec 可以增長到 10 個元素。
- 使用 iterator 方法創建一個 Vec:
let mut vec = (1..10).collect::<Vec<i32>>();
這會創建一個長度為 9、值為 [1, 2, 3, 4, 5, 6, 7, 8, 9] 的向量。
這些方法的主要區別在于:
- Vec::new() 和 vec![] 創建的向量初始長度為 0 或指定長度。
- Vec::with_capacity() 創建的向量初始長度為 0,但預分配了指定的容量。這可以提高向量在后續增長時的性能,因為不需要頻繁重新分配內存。
- 使用 iterator 方法創建的向量會根據 iterator 的長度創建指定大小的向量。
總的來說,如果你知道向量的大致大小,使用 Vec::with_capacity() 可以獲得最好的性能。否則,Vec::new() 和 vec![] 也是不錯的選擇。這里的區別類似Java。
操作Vec
在 Rust 中, Vec<T>
是一個可變長度的動態數組,用于存儲相同類型的多個值。它有以下主要的方法:
- 添加元素 - 使用
push()
方法:
let mut vec = Vec::new();
vec.push(1);
- 刪除元素 - 使用
pop()
方法刪除最后一個元素:
let mut vec = vec![1, 2, 3];
vec.pop(); // vec = [1, 2]
- 查找元素 - 使用索引(
[]
)訪問元素:
let vec = vec![1, 2, 3];
let elem = vec[0]; // elem = 1
- 修改元素 - 也使用索引(
[]
)訪問元素,然后對其進行修改:
let mut vec = vec![1, 2, 3];
vec[0] = 5;
// vec = [5, 2, 3]
除此之外, Vec<T>
還有其他方法,比如:
len()
- 獲取向量長度iter()
- 創建一個迭代器以便于遍歷元素iter_mut()
- 創建一個可變迭代器以便于修改元素first()
- 獲取第一個元素last()
- 獲取最后一個元素get()
- 安全地訪問一個元素,返回 Option<&T>get_mut()
- 安全地訪問一個可變元素,返回 Option<&mut T>
這些方法可以滿足你對 Vec<T>
的基本操作需求。
map
在 Rust 中,有幾種主要的 map 實現:
- HashMap - 散列表映射,基于哈希表實現。這是 Rust 中最常用的映射類型。
- BTreeMap - 二叉樹映射,基于二叉樹實現。鍵值是有序的。
- LinkedHashMap - 鏈表散列表映射,基于哈希表和雙向鏈表實現。鍵值保持插入順序。
- IndexMap - 基于數組的映射。鍵必須實現
Index
trait。 - FxHashMap - 散列表映射,使用第三方
fxhash
算法實現。性能可能優于標準庫的HashMap
這里主要介紹HashMap的用法,其他的大同小異,后續會出一個專門的用法介紹。
在 Rust 中,HashMap 的主要用法如下:
- 創建一個空的 HashMap:
let mut map = HashMap::new();
- 插入鍵值對:
map.insert(1, "a");
- 獲取值:
let a = map.get(&1); // a = Some("a")
- 刪除鍵值對:
map.remove(&1);
- 迭代 HashMap:
for (key, value) in &map {println!("{}: {}", key, value);
}
- 檢查鍵是否存在:
map.contains_key(&1); // true 或 false
- 獲取 HashMap 的長度:
let len = map.len();
- 清空 HashMap:
map.clear();
- 只迭代鍵或值:
for key in map.keys() {println!("{}", key);
}for value in map.values() {println!("{}", value);
}
這些是 HashMap 在 Rust 中最基本和最常用的方法。HashMap 是一個無序的 map,鍵類型必須實現 Eq
和 Hash
trait。
Set
- HashSet - 散列表集合,基于哈希表實現。用于存儲唯一的鍵。
- BTreeSet - 二叉樹集合,基于二叉樹實現。鍵值是有序的。
這里我們也重點介紹HashSet的用法:
- HashSet基礎用法:
let mut set = HashSet::new();
set.insert(1); // 插入元素:
set.remove(&1);// 刪除元素:
set.contains(&1); // 檢查元素是否存在: true 或 false
let len = set.len();// 獲取 HashSet 的長度:for elem in &set { // 迭代 HashSet:println!("{}", elem);
}set.clear(); // 清空 HashSet:
- 取兩個 HashSet 的交集、并集、差集:
let set1 = HashSet::from([1, 2, 3]);
let set2 = HashSet::from([2, 3, 4]);let intersection = set1.intersection(&set2).collect(); // [2, 3]
let union = set1.union(&set2).collect(); // [1, 2, 3, 4]
let difference = set1.difference(&set2).collect(); // [1]
HashSet 是一個無序的集合,元素類型必須實現 Eq
和 Hash
trait。
迭代器&流式編程(Iterator)
在 Rust 中,迭代器(Iterator)是一種用于遍歷集合元素的抽象。它提供了一個統一的接口,使你可以對各種不同類型的集合進行迭代。
要使用迭代器,你可以按照以下步驟進行操作:
- 創建一個迭代器:
你可以通過調用集合上的.iter()
或.iter_mut()
方法來創建一個不可變或可變的迭代器。例如:
let vec = vec![1, 2, 3];
let iter = vec.iter(); // 不可變迭代器
let mut_iter = vec.iter_mut(); // 可變迭代器
- 使用迭代器方法:
一旦你創建了迭代器,你可以使用迭代器的方法來處理集合的元素。一些常見的方法包括.next()
、.map()
、.filter()
、.fold()
等。例如:
let vec = vec![1, 2, 3];
let mut iter = vec.iter();// 使用 .next() 方法逐個獲取元素
while let Some(item) = iter.next() {println!("{}", item);
}// 使用 .map() 方法對元素進行轉換
let vec2: Vec<i32> = vec.iter().map(|x| x * 2).collect();
println!("{:?}", vec2); // 輸出 [2, 4, 6]// 使用 .filter() 方法過濾元素
let vec3: Vec<i32> = vec.iter().filter(|x| *x > 1).cloned().collect();
println!("{:?}", vec3); // 輸出 [2, 3]
- 鏈式調用迭代器方法:
你可以使用鏈式調用來組合多個迭代器方法,以實現復雜的操作。例如:
let vec = vec![1, 2, 3];
let result: i32 = vec.iter().filter(|x| *x > 1).map(|x| x * 2).sum();
println!("{}", result); // 輸出 10
通過這些方法,你可以對集合進行各種操作,如過濾、映射、折疊等。
- 自定義迭代器
在 Rust 中,你可以通過實現 Iterator
trait 來自定義迭代器。 Iterator
trait 有以下定義:
trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;// 默認方法fn size_hint(&self) -> (usize, Option<usize>) { ... }fn count(self) -> usize { ... }fn last(self) -> Option<Self::Item> { ... }fn nth(&mut self, n: usize) -> Option<Self::Item> { ... }fn step_by(self, step: usize) -> StepBy<Self> { ... }// 等等
}
要實現這個 trait,你需要:
-
定義
Item
類型,表示迭代器返回的元素類型。 -
實現
next
方法,返回迭代器的下一個元素,如果迭代器結束則返回None
。 -
可選:實現其他默認方法來改善迭代器的行為。
以下是一個自定義迭代器的示例:
struct Counter {count: u32,
}impl Iterator for Counter {type Item = u32;fn next(&mut self) -> Option<Self::Item> {if self.count < 5 {let c = self.count;self.count += 1;Some(c)} else {None}}
}fn main() {let mut iter = Counter { count: 0 };while let Some(i) = iter.next() {println!("{}", i);}
}
這個迭代器會返回 0 到 4 的數字,然后結束。我們實現了 next
方法來定義這個行為。
通過實現 Iterator
trait,你可以創建各種自定義的迭代器,以滿足不同的需求。希望這能幫助你理解 Rust 中的迭代器和如何自定義迭代器!如果還有其他問題,請隨時提問。
并發
多線程處理
下面我們通過一個例子學習: 創建一個線程,并每隔2s輸出“hello world”
上代碼:
use std::thread;
use std::time::Duration;fn main() {thread::spawn(|| {loop {println!("hello world");thread::sleep(Duration::from_secs(2));}});
}
這個代碼會:
- 使用
thread::spawn
創建一個新線程 - 在線程中有一個無限循環
- 每次循環會打印 “hello world”
- 使用
thread::sleep
使線程睡眠 2 秒 - 所以這個線程會每隔 2 秒打印一次 “hello world”
Duration::from_secs(2)
是創建一個表示 2 秒的 Duration。我們將其傳遞給 thread::sleep
來使線程睡眠 2 秒。
Ok,我們掌握了線程的基礎用法, 再來看一個復雜的例子: 用多線程實現觀察者模式
要實現觀察者模式,可以使用通道(channel)在線程間通信。例如:
use std::sync::mpsc;
use std::thread;struct Subject {observers: Vec<mpsc::Sender<i32>>,
}impl Subject {fn new() -> Subject {Subject { observers: vec![] }}fn attach(&mut self, observer: mpsc::Sender<i32>) {self.observers.push(observer);}fn notify(&self) {for observer in self.observers.iter() {observer.send(1).unwrap();}}
}fn main() {let (tx1, rx1) = mpsc::channel();let (tx2, rx2) = mpsc::channel();let mut subject = Subject::new();subject.attach(tx1);subject.attach(tx2);thread::spawn(move || {let msg = rx1.recv().unwrap();println!("Got: {}", msg);});thread::spawn(move || {let msg = rx2.recv().unwrap();println!("Got: {}", msg);});subject.notify();
}
在這個例子中:
- Subject 結構體充當主題(subject),維護多個觀察者(observers)的列表。
- attach 方法用于添加觀察者(通道的發送端)。
- notify 方法用于通知所有觀察者(通過通道發送消息)。
- 我們創建兩個線程作為觀察者,通過通道接收主題的通知。
- 當調用 subject.notify() 時,兩個觀察者線程會接收到通知并打印消息。
線程間通信
在上面的例子中,有一個新的知識點:使用通道(channel)在線程間通信。 那么什么是channel呢?
在 Rust 中,channel 用于在線程之間發送消息和通信。它可以在不同的線程之間安全地傳遞數據。
channel 的主要作用有:
-
線程間通信:channel 可以在不同的線程之間發送消息,用于線程間的通信和協作。
-
安全地共享數據:channel 可以在線程之間安全地傳遞數據,避免數據競爭。
-
限制并發:channel 的發送端和接收端各有一個緩存,這可以限制線程之間并發發送和接收消息的數量。
舉個例子:
use std::sync::mpsc::channel;
use std::thread;fn main() {let (tx, rx) = channel();thread::spawn(move || {tx.send(10).unwrap();});let received = rx.recv().unwrap();println!("Got: {}", received);
}
在這個例子中:
- 我們使用
channel()
創建一個 channel,它返回一個發送端tx
和一個接收端rx
。 - 然后我們創建一個線程,在線程中通過
tx
發送消息 10。 - 在主線程中,我們通過
rx
接收該消息,并打印結果。 - 這樣,通過 channel 我們就在兩個線程之間安全地傳遞了數據。
channel 在 Rust 的并發編程中非常有用,它可以用于線程池、工作竊取等并發模式中。
再看一個用法: 工作竊取
這里是一個使用 channel 實現工作竊取的例子:
use std::sync::mpsc::channel;
use std::thread;fn main() {let (tx, rx) = channel();let mut threads = vec![];for i in 0..10 {let tx = tx.clone();threads.push(thread::spawn(move || {let mut work = rx.recv().unwrap();while let Some(job) = work.recv() {println!("Worker {} got job {}", i, job);}}));}for job in 0..10 {let thread_id = job % threads.len();threads[thread_id].send(job).unwrap();}
}
這個例子做了以下工作:
-
創建一個
channel
,得到發送端 tx 和接收端 rx。 -
創建 10 個工作線程,每個線程從 rx 接收工作。
-
將 10 個工作(
job
)發送到不同的工作線程,實現工作竊取。每個工作會被發送到線程 ID 與工作 ID 取余后的線程。 -
每個工作線程接收工作,并打印接收到的工作 ID。
-
主線程將所有工作分發完成后結束。
這個例子展示了如何使用 channel 在線程之間傳遞工作,實現工作竊取的模式。每個工作線程從 channel 接收工作,而不是固定的工作隊列。這使得工作可以在線程之間動態分配,實現工作竊取。
線程池
熟悉java的同學可能會問了,有線程? 那有線程池嗎?必須有!!
在 Rust 中,你可以使用 threadpool
crate 來創建一個線程池。以下是一個基本的示例:
use threadpool::ThreadPool;fn main() {let pool = ThreadPool::new(4);for i in 0..10 {let j = i;pool.execute(move || {println!("Hello from thread {}", j);});}
}
這會創建一個包含 4 個線程的線程池。然后我們使用 pool.execute()
方法在線程池中執行 10 個閉包。這些閉包會被線程池的線程執行,并打印出執行線程的索引。
threadpool
crate 提供了以下主要功能:
ThreadPool::new(size)
:創建一個包含size
個線程的線程池。pool.execute(closure)
:在線程池的某個線程中執行提供的閉包。pool.join()
:等待線程池中的所有線程結束。ThreadPoolBuilder
:可以用來自定義線程池的各種設置,如線程名稱、堆棧大小等。
一個更復雜的例子:
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use threadpool::ThreadPool;fn main() {let pool = ThreadPool::new(4);let (tx, rx) = mpsc::channel();let tx = Arc::new(Mutex::new(tx));for i in 0..10 {let tx = tx.clone();pool.execute(move || {let mut tx = tx.lock().unwrap();tx.send(i).unwrap();});}for _ in 0..10 {let j = rx.recv().unwrap();println!("Got: {}", j);}pool.join();
}
這里我們使用了 mpsc
庫創建一個通道,并使用 Arc<Mutex<T>>
在線程之間共享該通道的發送端。然后我們在 10 個任務中發送數字到通道,并在主線程中接收這些數字。
序列化
在后端開發中,序列化是一個繞不過的話題,前后端交互,后端之間交互都需要對數據做序列化與反序列化。 在 Rust 中,有幾種常用的序列化方式:
-
Serde:這是 Rust 中最流行的序列化庫。它提供了多種序列化格式的支持,如 JSON、YAML、TOML 等。使用 Serde 可以方便地將 Rust 結構體序列化為這些格式,以及反序列化回 Rust 結構體。
-
Bincode:這是一個用于在 Rust 中進行二進制序列化的庫。它可以高效地將 Rust 的基本數據結構編碼為二進制,并解碼回原數據結構。
-
Ron:這是一個用于 Rust 對象表示法 (RON) 的序列化格式和解析器。RON 是一個人類友好的二進制序列化格式,設計用于在 Rust 中存儲和傳輸數據。
-
CBOR:這是一個實現了約束二進制對象表示法 (CBOR) 的 Rust 庫。CBOR 是一種二進制序列化格式,它比 JSON 更緊湊,也更適用于嵌入式系統。
-
MessagePack:這是一個實現 MessagePack 二進制序列化格式的 Rust 庫。MessagePack 是一個高效的二進制序列化格式,可以用于在不同語言之間交換數據。
-
Protobuf:這是 Google 開發的一種數據序列化格式,在 Rust 中可以使用
prost
或protobuf
庫來實現。Protobuf 是一種語言無關、平臺無關的可擴展機制,用于序列化結構化數據。
以上就是 Rust 中常用的幾種序列化方式。總的來說,如果你需要一個通用的序列化方式,可以選擇 Serde。如果你需要一個高效緊湊的二進制格式,可以選擇 Bincode、CBOR 或 MessagePack。如果你需要跨語言支持,可以選擇 Protobuf。
這里主要演示下Serde的用法,它提供了一組宏和 trait,用于將 Rust 數據結構轉換為各種格式的序列化表示,并將序列化表示轉換回 Rust 數據結構。
要使用 Serde,首先需要在 Cargo.toml
文件中添加以下依賴項:
[dependencies]
serde = "1.0"
serde_json = "1.0"
接下來,我們將給出一個使用 Serde 進行 JSON 序列化和反序列化的例子:
use serde::{Serialize, Deserialize};
use serde_json::{Result, Value};#[derive(Serialize, Deserialize, Debug)]
struct Person {name: String,age: u8,address: String,
}fn main() -> Result<()> {// 序列化為 JSONlet person = Person {name: "Alice".to_string(),age: 25,address: "123 ABC Street".to_string(),};let serialized = serde_json::to_string(&person)?;println!("Serialized: {}", serialized);// 反序列化為 Rust 結構體let deserialized: Person = serde_json::from_str(&serialized)?;println!("Deserialized: {:?}", deserialized);Ok(())
}
在上面的例子中,我們定義了一個 Person
結構體,并使用 #[derive(Serialize, Deserialize)]
宏為其實現了 Serde 的 Serialize
和 Deserialize
trait。這使得我們可以將 Person
結構體序列化為 JSON 字符串,并將 JSON 字符串反序列化為 Person
結構體。
在 main
函數中,我們首先創建一個 Person
實例,然后使用 serde_json::to_string
方法將其序列化為 JSON 字符串,并打印出來。接著,我們使用 serde_json::from_str
方法將 JSON 字符串反序列化為 Person
結構體,并打印出來。
網絡請求
請求Http接口
在 Rust 中,你可以使用第三方庫來進行 HTTP 請求。最常用的庫之一是 reqwest
,它提供了簡單且易于使用的 API 來發送 HTTP 請求。
首先,你需要在 Cargo.toml
文件中添加 reqwest
依賴項:
[dependencies]
reqwest = "0.11"
接下來,我們將給出一個使用 reqwest
發送 GET 請求的示例:
use reqwest::Error;#[tokio::main]
async fn main() -> Result<(), Error> {let response = reqwest::get("https://api.example.com/users").await?;let body = response.text().await?;println!("Response Body: {}", body);Ok(())
}
在上面的示例中,我們使用 reqwest::get
方法發送一個 GET 請求到 https://api.example.com/users
。然后,我們使用 response.text().await?
來獲取響應的文本內容,并打印出來。
需要注意的是,我們使用了 tokio::main
宏來異步運行請求。因此,你需要在 main
函數之前添加 tokio
作為依賴項,并在 main
函數前面使用 #[tokio::main]
注解。
到目前為止,我們學習了集合、并發、序列化和網絡請求,最后再留一個作業: 請求百度首頁,并解析出所有的http鏈接。 結合本文所學哦