输入/输出

锁定

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 默认是非缓冲的。如果你对文件或网络套接字进行了许多小的重复读取或写入调用,可以使用 BufReaderBufWriter。它们会维护一个内存缓冲区用于输入和输出,最大限度地减少所需的系统调用次数。

例如,将这段非缓冲写入器的代码:

#![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(())
}
}

示例 1, 示例 2

flush 的显式调用并非绝对必要,因为当 out 被丢弃时,会自动执行刷新。然而,在这种情况下,任何刷新时发生的错误都会被忽略,而显式刷新会使该错误显式化。

忘记进行缓冲在写入时更为常见。非缓冲和缓冲写入器都实现了 Write 特质,这意味着对于向非缓冲写入器和缓冲写入器写入的代码基本相同。相反,非缓冲读取器实现了 Read 特质,但缓冲读取器实现了 BufRead 特质,这意味着从非缓冲读取器和缓冲读取器读取的代码是不同的。例如,使用 BufRead::read_lineBufRead::lines 在非缓冲读取器中逐行读取文件是困难的,但在缓冲读取器中却很简单。因此,很难像上面的写入器那样为读取器编写一个示例,其中前后版本如此相似。

最后,注意缓冲也适用于标准输出(stdout),因此在向标准输出进行多次写入时,可能需要结合手动锁定和缓冲。

从文件读取行

本节 解释了如何在使用 BufRead 逐行读取文件时避免过多的分配。

将输入读取为原始字节

内置的 String 类型在内部使用 UTF-8,这会在将输入读入其中时增加一些小但非零的开销,因为需要进行 UTF-8 验证。如果你只想处理输入字节,而不必担心 UTF-8(例如,如果你处理的是 ASCII 文本),可以使用 BufRead::read_until

还有专门用于读取 字节导向的数据行 和处理 字节字符串 的 crates。