11.2 文件操作与I/O
文件操作与输入/输出(I/O)是任何实用程序的基础。Rust 的标准库提供了强大且安全的文件处理能力,其设计理念延续了 Rust 一贯的安全和零成本抽象原则。本章将深入探讨如何在 Rust 中进行文件读写、处理路径以及使用标准 I/O 流。
11.2.1 文件路径与 Path
在 Rust 中,文件路径操作主要依赖于 std::path::Path 和 std::path::PathBuf 类型。Path 是一个不可变的切片类型,而 PathBuf 是可变的、拥有所有权的路径类型。
use std::path::Path;
fn main() {
let path = Path::new("example.txt");
// 检查文件是否存在
println!("文件是否存在: {}", path.exists());
// 获取文件扩展名
if let Some(ext) = path.extension() {
println!("文件扩展名: {:?}", ext);
}
// 获取父目录
if let Some(parent) = path.parent() {
println!("父目录: {:?}", parent);
}
}
11.2.2 读取文件内容
Rust 提供了多种读取文件内容的方式,最常用的是 std::fs::read_to_string 和 std::fs::read。
读取文本文件
use std::fs;
fn main() -> std::io::Result<()> {
// 读取整个文件为字符串
let content = fs::read_to_string("hello.txt")?;
println!("文件内容:\n{}", content);
Ok(())
}
读取二进制文件
use std::fs;
fn main() -> std::io::Result<()> {
// 读取整个文件为字节向量
let data = fs::read("image.png")?;
println!("文件大小: {} 字节", data.len());
Ok(())
}
11.2.3 写入文件
写入文件同样简单直接,std::fs::write 可以一次性写入数据。
use std::fs;
fn main() -> std::io::Result<()> {
// 写入字符串到文件
fs::write("output.txt", "Hello, Rust I/O!")?;
// 写入字节数据
let bytes = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" 的 ASCII 码
fs::write("binary.dat", bytes)?;
Ok(())
}
11.2.4 使用 File 结构体进行精细控制
对于需要更精细控制的操作,如追加写入或随机访问,可以使用 std::fs::File 结构体。
打开与创建文件
use std::fs::{File, OpenOptions};
use std::io::Write;
fn main() -> std::io::Result<()> {
// 创建新文件(如果存在则覆盖)
let mut file = File::create("new_file.txt")?;
file.write_all(b"Creating a new file!")?;
// 以追加模式打开文件
let mut file = OpenOptions::new()
.append(true)
.open("existing_file.txt")?;
file.write_all(b"\nAppending new content")?;
Ok(())
}
读取文件内容
use std::fs::File;
use std::io::Read;
fn main() -> std::io::Result<()> {
let mut file = File::open("data.txt")?;
let mut content = String::new();
// 读取到字符串
file.read_to_string(&mut content)?;
println!("读取到的内容: {}", content);
// 逐行读取
// 注意:需要重新打开文件或使用 seek 回到文件开头
let mut file = File::open("data.txt")?;
let mut buffer = [0u8; 1024];
let bytes_read = file.read(&mut buffer)?;
println!("读取了 {} 字节", bytes_read);
Ok(())
}
11.2.5 目录操作
Rust 也提供了丰富的目录操作功能,包括创建、遍历和删除目录。
use std::fs;
fn main() -> std::io::Result<()> {
// 创建目录
fs::create_dir("my_directory")?;
// 递归创建目录
fs::create_dir_all("path/to/nested/directory")?;
// 读取目录内容
for entry in fs::read_dir(".")? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
println!("目录: {:?}", path);
} else if path.is_file() {
println!("文件: {:?}", path);
}
}
// 删除目录(目录必须为空)
fs::remove_dir("my_directory")?;
// 递归删除目录
fs::remove_dir_all("path")?;
Ok(())
}
11.2.6 标准 I/O 流
Rust 的标准 I/O 流(stdin、stdout、stderr)提供了与命令行交互的能力。
use std::io::{self, Write, BufRead};
fn main() -> io::Result<()> {
// 读取用户输入
let mut input = String::new();
println!("请输入你的名字:");
io::stdin().read_line(&mut input)?;
println!("你好, {}!", input.trim());
// 使用 BufRead 逐行读取
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line?;
if line == "quit" {
break;
}
println!("你输入了: {}", line);
}
// 写入到 stdout
writeln!(io::stdout(), "这是标准输出")?;
// 写入到 stderr
writeln!(io::stderr(), "这是错误输出")?;
Ok(())
}
11.2.7 文件 I/O 最佳实践
使用 BufReader 和 BufWriter 提高性能
对于大文件的读取和写入,使用缓冲读写器可以显著提升性能。
use std::fs::File;
use std::io::{BufReader, BufWriter, Write, BufRead};
fn main() -> std::io::Result<()> {
// 带缓冲的读取
let file = File::open("large_file.txt")?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
// 处理每一行
}
// 带缓冲的写入
let file = File::create("output.txt")?;
let mut writer = BufWriter::new(file);
writer.write_all(b"Buffered write operation")?;
Ok(())
}
错误处理
始终使用 Result 类型处理文件操作,因为文件 I/O 极易出错。
use std::fs::File;
use std::io::{self, Read};
fn read_config_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_config_file("config.toml") {
Ok(content) => println!("配置内容: {}", content),
Err(e) => eprintln!("读取配置文件失败: {}", e),
}
}
通过掌握这些文件操作与 I/O 技术,你将能够在 Rust 中高效地处理各种数据持久化和输入输出需求。记住,Rust 的所有权系统和类型安全特性同样适用于文件操作,确保你的程序在运行时不会出现内存安全问题。
