一、為什么使用帶緩沖的讀寫?
在 Rust 的異步編程中,AsyncReadExt 和 AsyncWriteExt 提供了方便的讀寫方法。然而,每次調用這些方法,都會向操作系統發起系統調用。如果處理大量數據時,每次只讀或寫少量字節,就會頻繁地切換上下文,造成 ?CPU 時間浪費?,降低 IO 效率。
緩沖讀寫的好處
- ?減少系統調用次數?:數據會先寫入緩沖區,當緩沖滿了或條件滿足時才向操作系統發起系統調用。
- ?按行讀取?:緩沖讀操作可以識別換行符并按行讀取數據,這比按字節讀取更方便。
- ?高效的數據處理?:減少 CPU 資源浪費,提高 IO 性能。
二、使用 BufReader 和 BufWriter 實現緩沖讀寫
tokio::io 模塊提供了 BufReader 和 ?BufWriter,用于為 Reader 和 Writer 添加緩沖功能。
BufReader 讀取文件按行打印
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, runtime};
?
fn main() {
let rt = runtime::Runtime::new().unwrap();
rt.block_on(async {
let f = File::open("a.log").await.unwrap();
let mut lines = BufReader::new(f).lines();
?
while let Some(line) = lines.next_line().await.unwrap() {
println!("read line: {}", line);
}
});
}
?解釋?:
BufReader讀取文件時,會將內容緩存在內部緩沖區中。- ?按行讀取?:
lines()方法將內容按行分割,next_line()異步獲取下一行。
BufWriter 寫文件
use tokio::{fs::File, io::{AsyncWriteExt, BufWriter}, runtime};
?
fn main() {
let rt = runtime::Runtime::new().unwrap();
rt.block_on(async {
let f = File::create("output.txt").await.unwrap();
let mut writer = BufWriter::new(f);
?
writer.write_all(b"Hello, world!\n").await.unwrap();
writer.flush().await.unwrap(); // 確保緩沖區數據寫入文件
});
}
?解釋?:
BufWriter將數據寫入緩沖區,當緩沖區滿或調用flush()時,才會將數據寫入文件。- ?**
write_all()**?:寫入字節數據。
三、按自定義分隔符讀取數據:split()
split() 可以將讀取的內容按指定的字節分隔符分割成多個片段。
示例:按換行符分割文件
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, runtime};
?
fn main() {
let rt = runtime::Runtime::new().unwrap();
rt.block_on(async {
let f = File::open("a.log").await.unwrap();
let mut segments = BufReader::new(f).split(b'\n');
?
while let Some(segment) = segments.next_segment().await.unwrap() {
println!("segment: {}", String::from_utf8(segment).unwrap());
}
});
}
?解釋?:
- ?**
split(b'\n')**?:按換行符分割文件內容。 - **每次調用 **
next_segment()獲取一個片段,返回Vec<u8>。
四、隨機讀寫文件:Seek
tokio::io::AsyncSeekExt 提供了 隨機讀寫 功能,可以設置文件的偏移位置,實現非順序的讀寫。
示例:設置偏移位置讀取文件
use std::io::SeekFrom;
use tokio::{fs::File, io::{AsyncReadExt, AsyncSeekExt}, runtime};
?
fn main() {
let rt = runtime::Runtime::new().unwrap();
rt.block_on(async {
let mut f = File::open("a.log").await.unwrap();
?
f.seek(SeekFrom::Start(5)).await.unwrap(); // 從第5個字節開始讀取
let mut content = String::new();
f.read_to_string(&mut content).await.unwrap();
println!("Data: {}", content);
?
f.rewind().await.unwrap(); // 將偏移指針重置到文件開頭
});
}
?解釋?:
- ?**
seek()**?:將偏移指針移動到指定位置。 - ?**
rewind()**?:重置偏移指針到文件開頭。
五、標準輸入輸出
tokio::io 提供了 stdin() 和 ?**stdout()**?,用于異步讀取標準輸入和輸出。
示例:讀取用戶輸入并回顯
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, runtime};
?
fn main() {
let rt = runtime::Runtime::new().unwrap();
rt.block_on(async {
let mut stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
?
let mut buffer = vec![0; 1024];
loop {
stdout.write(b"Enter something: ").await.unwrap();
stdout.flush().await.unwrap();
?
let n = stdin.read(&mut buffer).await.unwrap();
if n == 0 {
break; // 用戶輸入結束
}
?
stdout.write(&buffer[..n]).await.unwrap();
stdout.flush().await.unwrap();
}
});
}
?解釋?:
- ?讀取用戶輸入?:
stdin.read()從標準輸入讀取數據。 - ?輸出到終端?:
stdout.write()將數據回顯。
六、全雙工通信:DuplexStream
tokio::io::duplex() 提供了類似套接字的全雙工管道,支持讀寫同時進行。
示例:模擬客戶端和服務端的通信
use tokio::{io::{self, AsyncReadExt, AsyncWriteExt}, runtime, time};
?
#[tokio::main]
async fn main() {
let (mut client, mut server) = io::duplex(64); // 創建雙向管道
?
// 啟動一個任務:服務端發送消息
tokio::spawn(async move {
server.write_all(b"Hello from server").await.unwrap();
});
?
// 客戶端讀取來自服務端的消息
let mut buf = vec![0; 64];
let n = client.read(&mut buf).await.unwrap();
println!("Client received: {}", String::from_utf8_lossy(&buf[..n]));
}
?解釋?:
- ?**
duplex()**?:創建全雙工讀寫管道。 - ?客戶端和服務端通信?:服務端寫入數據,客戶端讀取數據。
七、拆分 Reader 和 Writer:split()
split() 方法可以將一個可讀寫的目標(如 TcpStream)拆分為 讀半部分 和 ?寫半部分?。
示例:拆分 DuplexStream
use tokio::{io::{self, AsyncReadExt, AsyncWriteExt}, runtime};
?
#[tokio::main]
async fn main() {
let (client, server) = io::duplex(64);
?
let (mut reader, writer) = io::split(client); // 拆分為讀和寫部分
?
// 關閉不使用的寫部分
drop(writer);
?
let mut buf = vec![0; 64];
let n = reader.read(&mut buf).await.unwrap();
println!("Read from server: {}", String::from_utf8_lossy(&buf[..n]));
}
八、總結
**在這一部分,教程介紹了如何使用 **Tokio 實現異步 IO,包括:
- ?**
BufReader和BufWriter**?:用于高效的緩沖讀寫。 - ?w按行讀取和自定義分隔符?:方便處理文本數據。
- ?隨機讀寫文件?:通過設置偏移指針實現非順序讀寫。
- ?標準輸入輸出?:異步處理用戶輸入和終端輸出。
- ?全雙工通信?:通過
DuplexStream實現雙向數據傳輸。 - ?拆分 Reader 和 Writer?:提高代碼的靈活性。w