輸入輸出:stdin、檔案讀寫
本集目標
認識 Rust 的輸入輸出方法,學會讀寫檔案。
概念說明
前面學了怎麼用 Path 表示檔案位置、怎麼處理字串內容,這集來學怎麼實際讀寫檔案。
回顧 stdin
第 1 章我們照抄了這三行來讀取使用者輸入:
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("讀取失敗");
let name = input.trim();
}
你已經學過 String、&mut 和 .expect(),這段應該都看得懂了。唯一沒提過的是 std::io::stdin() 回傳一個 Stdin struct,而 read_line 回傳的是 io::Result<usize>——這是 Result<usize, io::Error> 的型別別名,標準庫裡很多輸入輸出函數都用它。
最簡單的檔案讀寫
fs::read_to_string 的參數型別是 impl AsRef<Path>——第 1 集學的 AsRef 在這裡派上用場。你可以傳 &str、String、&Path、PathBuf 都行,不用手動轉換。
讀整個檔案成字串:
use std::fs;
fn main() {
let content = fs::read_to_string("hello.txt").expect("讀取失敗");
println!("{}", content);
}
寫入檔案(檔案不存在會建立,存在會覆蓋):
use std::fs;
fn main() {
fs::write("output.txt", "Hello, file!").expect("寫入失敗");
}
這兩個函數夠簡單,但 read_to_string 會把整個檔案一次讀進記憶體——如果檔案很大就不適合。
File + BufReader:逐行讀取
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let file = File::open("data.txt").expect("開啟失敗");
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line.expect("讀取行失敗");
println!("{}", line);
}
}
File::open 開啟檔案,BufReader 包在外面提供緩衝區,.lines() 逐行讀取,每行是一個 io::Result<String>。
寫入檔案
use std::fs::File;
use std::io::Write;
fn main() {
let mut file = File::create("output.txt").expect("建立失敗");
writeln!(file, "第一行").expect("寫入失敗");
writeln!(file, "第二行").expect("寫入失敗");
}
File::create 建立(或覆蓋)檔案,writeln! 跟 println! 很像,只是輸出目標從螢幕換成檔案。要用 writeln! 需要引入 Write trait。
Read、Write、BufRead
標準庫用 trait 抽象化輸入輸出:
Read:能讀取 bytes 的東西(File、Stdin、TcpStream等)Write:能寫入 bytes 的東西(File、Stdout、TcpStream等)BufRead:帶緩衝的讀取,提供lines()等方法。BufReader可以把任何Read變成BufRead
這就是為什麼很多函數的參數寫成 impl Read 或 impl Write——不管你傳檔案、stdin 還是網路連線,只要實作了對應的 trait 就能用。
範例程式碼
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Write};
fn main() -> io::Result<()> {
// 寫入檔案
let mut file = File::create("names.txt")?;
writeln!(file, "Alice")?;
writeln!(file, "Bob")?;
writeln!(file, "Charlie")?;
// 一次讀取整個檔案
let all = fs::read_to_string("names.txt")?;
println!("整個檔案:\n{}", all);
// 逐行讀取
let file = File::open("names.txt")?;
let reader = BufReader::new(file);
for (i, line) in reader.lines().enumerate() {
println!("第 {} 行:{}", i + 1, line?);
}
Ok(())
}
重點整理
io::Result<T>是Result<T, io::Error>的型別別名fs::read_to_string/fs::write:最簡單的一行讀寫File::open+BufReader:逐行讀取大檔案File::create+writeln!:逐行寫入Read、Write、BufRead是輸入輸出的核心trait,讓不同來源(檔案、stdin、網路)用同一套介面