12 I/O

输入输出性能

译文 · 基于 The Rust Performance Book

I/O

原文链接: https://nnethercote.github.io/perf-book/io.html

加锁

Rust 的 print! 和 println! 宏在每次调用时都会锁定 stdout。若频繁调用这些宏,手动锁定 stdout 可能更好。

例如,把下面这段代码:

1
2
3
4
# let lines = vec!["one", "two", "three"];
for line in lines {
    println!("{}", line);
}

改成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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` 被丢弃时 stdout 会解锁
# Ok(())
# }

stdin 和 stderr 在重复操作时同样可以锁定。

缓冲

Rust 的文件 I/O 默认无缓冲。若对文件或网络套接字有大量小而重复的读/写调用,应使用 BufReader 或 BufWriter。它们在内存中维护输入/输出缓冲区,从而减少所需的系统调用次数。

例如,把下面这段无缓冲写入代码:

1
2
3
4
5
6
7
8
9
# 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(())
# }

改成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 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 被丢弃时会自动刷新。不过那样刷新时的任何错误都会被忽略,而显式 flush 会让错误明确暴露。

忘记使用缓冲在写入时更常见。无缓冲和缓冲写入器都实现 Write trait,因此写入无缓冲写入器和缓冲写入器的代码非常相似。相比之下,无缓冲读取器实现 Read trait,而缓冲读取器实现 BufRead trait,因此从无缓冲读取器和缓冲读取器读取的代码不同。例如,用无缓冲读取器逐行读文件很困难,但用缓冲读取器通过 BufRead::read_line 或 BufRead::lines 则很简单。因此,很难像上面写入示例那样为读取器写「前后对比」示例——前后版本会非常相似。

最后注意,缓冲也适用于 stdout,因此对 stdout 大量写入时,可以结合手动加锁和缓冲。

从文件逐行读取

本节说明如何在使用 BufRead 逐行读取文件时避免过多分配。

以原始字节读取输入

内置的 String 类型内部使用 UTF-8,读入输入时会因 UTF-8 校验带来虽小但非零的开销。若只想处理输入字节而不关心 UTF-8(例如处理 ASCII 文本),可以使用 BufRead::read_until。

也有专门用于读取面向字节的行数据和处理字节字符串的 crate。

最后修改 August 23, 2026: 更新 (499855b16)