std::io 模块教程
Rust 的 std::io
模块是标准库的核心组成部分,
用于处理各种输入/输出(I/O)操作。
它提供了抽象的 trait 如 Read
和 Write
,
允许开发者统一处理文件、网络、内存和标准流等不同 I/O 来源。std::io
强调安全性,通过 io::Result
(Result<T, io::Error>
的别名)强制显式错误处理,避免未检查的异常。该模块与 std::fs
(文件系统操作)、std::net
(网络 I/O)和 std::process
(子进程 I/O)紧密集成,支持跨平台开发(Windows、Unix 等)。
1. std::io 简介
- 导入和基本结构:通常用
use std::io;
或指定如use std::io::{Read, Write};
。模块分为 trait、类型和函数三大类。- Trait 概述:
Read
:从源读取字节,支持read
、read_exact
、read_to_end
等方法。Write
:向目标写入字节,支持write
、write_all
、flush
。BufRead
:缓冲读取扩展Read
,添加read_line
、lines
。Seek
:定位流,支持seek
以移动读/写位置。
- 类型:
io::Error
(带kind()
如ErrorKind::NotFound
)、BufReader
/BufWriter
(缓冲器)、Cursor
(内存流)、Stdin
/Stdout
/Stderr
(标准流)、Bytes
/Chain
/Take
等适配器。 - 函数:
copy
(流复制)、empty
(空读取器)、repeat
(重复字节)、sink
(丢弃写入器)。
- Trait 概述:
- 设计哲学:
std::io
是零成本抽象,编译时内联;错误通过枚举ErrorKind
分类,便于模式匹配。 - 跨平台注意:Windows 用 UTF-16 路径,但
io
抽象它;Unix 支持非阻塞 I/O。 - 性能基础:小 I/O 操作开销大,用缓冲减少系统调用。
- 常见用例:CLI 输入、日志写入、文件处理、网络数据流。
2. 标准输入/输出/错误流
标准流是进程与环境的接口。stdin
用于输入,stdout
/stderr
用于输出。它们是锁定的(线程安全),但默认缓冲可能导致延迟。
示例:基本标准输入(单行)
use std::io::{self, BufRead}; fn main() -> io::Result<()> { let stdin = io::stdin(); let mut input = String::new(); stdin.read_line(&mut input)?; println!("你输入: {}", input.trim()); Ok(()) }
- 解释:
stdin()
返回Stdin
(实现Read
和BufRead
)。read_line
读取到 \n,包括换行符;trim
移除空白。错误如中断(Ctrl+C)返回ErrorKind::Interrupted
。
示例:多行输入循环(扩展处理 EOF)
use std::io::{self, BufRead}; fn main() -> io::Result<()> { let stdin = io::stdin(); let mut lines = stdin.lines(); let mut count = 0; loop { match lines.next() { Some(Ok(line)) => { count += 1; println!("行 {}: {}", count, line); } Some(Err(e)) => return Err(e), // 传播错误 None => break, // EOF(如 Ctrl+D) } } println!("总行数: {}", count); Ok(()) }
- 解释:
lines()
是懒惰迭代器,返回Result<String>
。循环处理 EOF(None)和错误。性能:缓冲内部处理大输入。陷阱:不 flush stdout 可能延迟输出。
示例:标准输出和错误(带 flush 和格式化)
use std::io::{self, Write}; fn main() -> io::Result<()> { let mut stdout = io::stdout().lock(); // 锁定以高效写入 let mut stderr = io::stderr(); write!(&mut stdout, "正常输出: ")?; writeln!(&mut stdout, "值 {}", 42)?; stdout.flush()?; // 立即发送 writeln!(&mut stderr, "错误消息")?; Ok(()) }
- 解释:
lock()
返回锁守卫,提高连续写入效率。write!
/writeln!
是格式化宏。flush
必要于无缓冲场景。stderr 用于日志分离。扩展:用eprintln!
宏简化 stderr。
3. Read 和 Write Trait
这些 trait 是 I/O 的基础,允许泛型代码处理任何来源。
示例:从文件读取字节(基本 Read)
use std::fs::File; use std::io::Read; fn main() -> std::io::Result<()> { let mut file = File::open("data.bin")?; let mut buffer = [0u8; 1024]; // 固定缓冲 let bytes_read = file.read(&mut buffer)?; println!("读取 {} 字节: {:?}", bytes_read, &buffer[..bytes_read]); Ok(()) }
- 解释:
read
填充缓冲,返回读取字节数(可能少于缓冲大小)。EOF 返回 Ok(0)。性能:循环 read 直到 0 处理大文件。
示例:完整读取文件(read_to_end 扩展)
use std::fs::File; use std::io::Read; fn main() -> std::io::Result<()> { let mut file = File::open("text.txt")?; let mut contents = Vec::new(); file.read_to_end(&mut contents)?; println!("内容: {}", String::from_utf8_lossy(&contents)); Ok(()) }
- 解释:
read_to_end
读取所有剩余字节到 Vec。read_to_string
类似但转为 String,处理 UTF-8。陷阱:大文件可能 OOM;用流处理代替。
示例:写入字节(基本 Write)
use std::fs::File; use std::io::Write; fn main() -> std::io::Result<()> { let mut file = File::create("output.bin")?; let data = b"binary data"; let bytes_written = file.write(data)?; assert_eq!(bytes_written, data.len()); // 检查完整写入 file.flush()?; Ok(()) }
- 解释:
write
返回写入字节数,可能部分(中断时)。write_all
循环直到全部写入。flush
提交到磁盘。
示例:自定义类型实现 Read/Write
use std::io::{self, Read, Write}; #[derive(Debug)] struct ReverseReader<'a>(&'a [u8]); impl<'a> Read for ReverseReader<'a> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { let n = std::cmp::min(buf.len(), self.0.len()); for (i, &byte) in self.0[..n].iter().rev().enumerate() { buf[i] = byte; } self.0 = &self.0[n..]; Ok(n) } } fn main() -> io::Result<()> { let data = b"hello"; let mut reader = ReverseReader(data); let mut buf = vec![0; 5]; reader.read_exact(&mut buf)?; println!("反转读取: {:?}", String::from_utf8_lossy(&buf)); // "olleh" Ok(()) }
- 解释:自定义 Read 反转字节。生命周期
'a
确保借用安全。扩展:实现 Write 用于日志记录器。
4. 缓冲 I/O:BufReader 和 BufWriter
缓冲减少系统调用,提高效率(默认 8KB)。
示例:BufReader 读取大文件行(性能分析)
use std::fs::File; use std::io::{self, BufRead, BufReader}; use std::time::Instant; fn main() -> io::Result<()> { let start = Instant::now(); let file = File::open("large_log.txt")?; let reader = BufReader::with_capacity(64 * 1024, file); // 自定义 64KB 缓冲 let line_count = reader.lines().count(); println!("行数: {}, 耗时: {:?}", line_count, start.elapsed()); Ok(()) }
- 解释:
with_capacity
自定义缓冲大小。lines().count()
高效计数。性能:无缓冲读小块慢;缓冲适合顺序访问。陷阱:随机访问用 Seek,但缓冲可能失效。
示例:BufWriter 批量写入(带错误回滚)
use std::fs::File; use std::io::{self, BufWriter, Write}; fn main() -> io::Result<()> { let file = File::create("log.txt")?; let mut writer = BufWriter::new(file); for i in 0..10000 { if writeln!(&mut writer, "日志 {}", i).is_err() { // 错误时尝试 flush,但实际中用 transaction let _ = writer.flush(); break; } } writer.flush()?; // 提交所有 Ok(()) }
- 解释:批量 writeln 缓冲写入。
flush
在结束或错误时调用。扩展:错误时缓冲可能丢失部分数据;用外部 crate 如 atomicwrites 处理原子性。
5. Seek Trait 和 Cursor
Seek
允许非顺序访问,Cursor
用于内存测试。
示例:Seek 在文件中定位(随机访问)
use std::fs::File; use std::io::{self, Read, Seek, SeekFrom}; fn main() -> io::Result<()> { let mut file = File::open("data.txt")?; file.seek(SeekFrom::End(-10))?; // 从末尾倒数 10 字节 let mut buf = vec![0; 10]; file.read_exact(&mut buf)?; println!("最后 10 字节: {}", String::from_utf8_lossy(&buf)); Ok(()) }
- 解释:
SeekFrom::End(offset)
相对末尾。Current
用于当前位置。陷阱:管道或网络流不支持 Seek(返回 Unsupported)。
示例:Cursor 用于内存 I/O 测试(扩展模拟文件)
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; fn main() -> io::Result<()> { let mut cursor = Cursor::new(Vec::new()); cursor.write_all(b"test data")?; cursor.seek(SeekFrom::Start(5))?; let mut buf = vec![0; 4]; cursor.read_exact(&mut buf)?; println!("从位置 5: {}", String::from_utf8_lossy(&buf)); // "data" // 扩展:获取内部 Vec let inner = cursor.into_inner(); println!("完整数据: {:?}", inner); Ok(()) }
- 解释:
Cursor<Vec<u8>>
像可变文件。into_inner
获取底层数据。用于单元测试 I/O 代码无实际文件。
6. 错误处理和 io::Error
io::Error
有 kind()
、raw_os_error()
等,用于细粒度处理。
示例:详细错误分类(重试逻辑)
use std::fs::File; use std::io::{self, Read}; use std::thread::sleep; use std::time::Duration; fn read_with_retry(path: &str, retries: u32) -> io::Result<Vec<u8>> { let mut attempts = 0; loop { match File::open(path) { Ok(mut file) => { let mut contents = Vec::new(); file.read_to_end(&mut contents)?; return Ok(contents); } Err(e) => { match e.kind() { io::ErrorKind::NotFound => return Err(e), // 不可恢复 io::ErrorKind::PermissionDenied => return Err(e), io::ErrorKind::Interrupted | io::ErrorKind::TimedOut => { attempts += 1; if attempts > retries { return Err(e); } sleep(Duration::from_secs(1)); // 重试 } _ => return Err(e), // 其他错误 } } } } } fn main() { match read_with_retry("temp.txt", 3) { Ok(data) => println!("数据: {:?}", data), Err(e) => println!("最终错误: {} ({:?})", e, e.kind()), } }
- 解释:
kind()
分类错误,重试可恢复的如 Interrupted。raw_os_error
获取 OS 错误码(errno)。扩展:日志 e.to_string() 或 e.source() 链错误。
7. 高级主题:Copy、适配器和自定义 I/O
io::copy
:高效复制,支持缓冲。- 适配器:
io::Chain
(链流)、io::Take
(限制字节)、io::Bytes
(字节迭代)。
示例:io::copy 与适配器
use std::io::{self, copy, Chain, Cursor, Read, Take}; fn main() -> io::Result<()> { let header = Cursor::new(b"header\n"); let body = Cursor::new(b"body content that is long"); let limited_body = body.take(10); // 限制 10 字节 let mut chained = Chain::new(header, limited_body); let mut sink = io::sink(); // 丢弃写入 let copied = copy(&mut chained, &mut sink)?; println!("复制字节: {}", copied); // 17 (header 7 + 10 body) Ok(()) }
- 解释:
Chain
连接流。Take
限制读取。sink
丢弃数据,用于测试。empty
是空 Read。
示例:自定义适配器(加密读取器)
use std::io::{self, Read}; struct XorReader<R: Read> { inner: R, key: u8, } impl<R: Read> XorReader<R> { fn new(inner: R, key: u8) -> Self { Self { inner, key } } } impl<R: Read> Read for XorReader<R> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { let n = self.inner.read(buf)?; for byte in &mut buf[..n] { *byte ^= self.key; // 简单 XOR 加密 } Ok(n) } } fn main() -> io::Result<()> { let data = Cursor::new(b"secret"); let mut encrypted = XorReader::new(data, 0xAA); let mut output = vec![0; 6]; encrypted.read_exact(&mut output)?; println!("加密: {:?}", output); // XOR 结果 Ok(()) }
- 解释:包装 Read 应用 XOR。扩展自定义 I/O,如压缩或日志。
8. 最佳实践和常见陷阱
- 缓冲策略:默认 8KB 适合大多数;大文件用更大容量。避免无缓冲小读(高开销)。
- 错误最佳实践:分类 kind();重试 Transient 错误;日志完整 e(包括 backtrace 用 anyhow crate)。
- 性能陷阱:循环小 read/write 慢(用缓冲);flush 过多减速(批量后 flush)。
- 安全性:验证输入大小避免缓冲溢出;处理 Interrupted(信号)以重试。
- 跨平台扩展:Windows 行结束 \r\n,Unix \n;用 lines() 抽象。
- 测试扩展:用 Cursor/Vec 测试 Read/Write 无文件;mock trait 用 trait objects。
- 与异步:同步 io 阻塞;用 tokio::io 异步版本。
- 资源管理:drop 时自动关闭,但显式 flush/close 好习惯。
- 常见错误扩展:
- 部分写入:write 返回 < len,循环 write_all。
- InvalidData:非 UTF-8,用 from_utf8_lossy 处理。
- WouldBlock:非阻塞模式,返回后重试。
- Os 特定:用 raw_os_error 检查 errno。
练习建议
- 编写 CLI:从 stdin 读取 JSON,解析并写 stdout,用 BufReader 和 serde (外部,但模拟 io)。
- 实现日志旋转:用 Write 写入文件,检查大小后 Seek 重置。
- 创建管道模拟:用 Chain 和 Take 组合流,copy 到 BufWriter。
- 处理错误重试:写函数读网络流(模拟 Cursor),重试 TimedOut 3 次。
- 基准测试:比较 BufReader vs 裸 Read 大文件时间,用 Instant。