I/O

Locking

Rust的print!println!宏在每次调用时锁定stdout。如果你需要重复调用这些宏,最好手动锁定stdout。

例如,修改这段代码。

#![allow(unused)]
fn main() {
let lines = vec!["one", "two", "three"];
for line in lines {
    println!("{}", line);
}
}

to this:

#![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)?;
}
// stdout is unlocked when `lock` is dropped
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").unwrap();
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 = std::fs::File::create("test.txt")?;
let mut buf = BufWriter::new(out);
for line in lines {
    writeln!(buf, "{}", line)?;
}
buf.flush()?;
Ok(())
}
}

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

在编写时忘记使用缓冲区是比较常见的。无缓冲和有缓冲的写入器都实现了 [Write] trait,这意味着向无缓冲写入器和有缓冲写入器写入的代码基本相同。相比之下,无缓冲读取器实现了 [Read] trait,但有缓冲读取器实现了 [BufRead] trait,这意味着从无缓冲读取器和有缓冲读取器读取的代码是不同的。例如,使用无缓冲读取器逐行读取文件是困难的,但使用有缓冲读取器通过 [BufRead::read_line] 或 [BufRead::lines] 则很简单。因此,对于读取器来说,很难像对写入器那样编写一个示例,其中之前和之后的版本是如此相似。

最后,请注意,缓冲也适用于标准输出(stdout),因此当向标准输出频繁写入时,您可能希望结合手动锁定和缓冲。

Reading Lines from a File

这一部分解释了如何在使用 [BufRead] 逐行读取文件时避免过多的内存分配。

Reading Input as Raw Bytes

内置的String类型在内部使用UTF-8,当你读取输入到string类型时,会增加一个由UTF-8验证引起的小但非零的开销。如果你只想处理输入字节而不担心UTF-8(例如,如果你处理ASCII文本),你可以使用BufRead::read_until

还有专门的箱子用于读取面向字节的数据行和处理byte strings