输入/输出
锁定
Rust 的 print!
和 println!
宏在每次调用时都会锁定标准输出(stdout)。如果你重复调用这些宏,手动锁定标准输出可能会更好。
例如,将这段代码改为:
#![allow(unused)] fn main() { let lines = vec!["one", "two", "three"]; for line in lines { println!("{}", line); } }
改为这样:
#![allow(unused)] fn main() { fn blah() -> Result<(), std::io::Error> { let lines = vec!["one", "two", "three"]; use std::io::Write; let mut stdout = std::io::stdout(); let mut lock = stdout.lock(); for line in lines { writeln!(lock, "{}", line)?; } // 当 `lock` 被丢弃时,标准输出会解锁 Ok(()) } }
当对标准输入(stdin)和标准错误(stderr)进行重复操作时,同样可以进行锁定。
缓冲
Rust 的文件 I/O 默认是非缓冲的。如果你对文件或网络套接字进行了许多小的重复读取或写入调用,可以使用 BufReader
或 BufWriter
。它们会维护一个内存缓冲区用于输入和输出,最大限度地减少所需的系统调用次数。
例如,将这段非缓冲写入器的代码:
#![allow(unused)] fn main() { fn blah() -> Result<(), std::io::Error> { let lines = vec!["one", "two", "three"]; use std::io::Write; let mut out = std::fs::File::create("test.txt")?; for line in lines { writeln!(out, "{}", line)?; } Ok(()) } }
改为这样:
#![allow(unused)] fn main() { fn blah() -> Result<(), std::io::Error> { let lines = vec!["one", "two", "three"]; use std::io::{BufWriter, Write}; let mut out = BufWriter::new(std::fs::File::create("test.txt")?); for line in lines { writeln!(out, "{}", line)?; } out.flush()?; Ok(()) } }
对 flush
的显式调用并非绝对必要,因为当 out
被丢弃时,会自动执行刷新。然而,在这种情况下,任何刷新时发生的错误都会被忽略,而显式刷新会使该错误显式化。
忘记进行缓冲在写入时更为常见。非缓冲和缓冲写入器都实现了 Write
特质,这意味着对于向非缓冲写入器和缓冲写入器写入的代码基本相同。相反,非缓冲读取器实现了 Read
特质,但缓冲读取器实现了 BufRead
特质,这意味着从非缓冲读取器和缓冲读取器读取的代码是不同的。例如,使用 BufRead::read_line
或 BufRead::lines
在非缓冲读取器中逐行读取文件是困难的,但在缓冲读取器中却很简单。因此,很难像上面的写入器那样为读取器编写一个示例,其中前后版本如此相似。
最后,注意缓冲也适用于标准输出(stdout),因此在向标准输出进行多次写入时,可能需要结合手动锁定和缓冲。
从文件读取行
本节 解释了如何在使用 BufRead
逐行读取文件时避免过多的分配。
将输入读取为原始字节
内置的 String 类型在内部使用 UTF-8,这会在将输入读入其中时增加一些小但非零的开销,因为需要进行 UTF-8 验证。如果你只想处理输入字节,而不必担心 UTF-8(例如,如果你处理的是 ASCII 文本),可以使用 BufRead::read_until
。