Rust 1.x中读写文件的事实标准方法是什么?

301

Rust是一种相对较新的编程语言,因此我见过太多读写文件的方式。很多都是某些人为了博客所编写的极其混乱的代码片段,而且我找到的99%的例子(即使在Stack Overflow上)都来自不再工作的不稳定的构建版本。现在Rust已经稳定了,有没有一个简单、易读且不会引起恐慌的代码片段可用于读取或写入文件呢?

这是我最接近能正常工作的文本文件读取代码段,但是它仍然无法编译,尽管我相当确定我已经包含了所有该有的内容。这是基于我在Google+上发现的代码片段,我唯一更改的是旧的BufferedReader现在变成了BufReader

use std::fs::File;
use std::io::BufReader;
use std::path::Path;

fn main() {
    let path = Path::new("./textfile");
    let mut file = BufReader::new(File::open(&path));
    for line in file.lines() {
        println!("{}", line);
    }
}

编译器抱怨:

error: the trait bound `std::result::Result<std::fs::File, std::io::Error>: std::io::Read` is not satisfied [--explain E0277]
 --> src/main.rs:7:20
  |>
7 |>     let mut file = BufReader::new(File::open(&path));
  |>                    ^^^^^^^^^^^^^^
note: required by `std::io::BufReader::new`

error: no method named `lines` found for type `std::io::BufReader<std::result::Result<std::fs::File, std::io::Error>>` in the current scope
 --> src/main.rs:8:22
  |>
8 |>     for line in file.lines() {
  |>                      ^^^^^

总的来说,我需要的是:

  • 简洁性
  • 易读性
  • 涵盖所有可能出现的错误
  • 不要惊慌

2
你想如何读取文件?你想逐行读取,就像你展示的那样吗?还是你想将它全部读入一个字符串中?“读取文件”有不止一种方法。 - Shepmaster
3
任何一种方式都可以。我故意让它保持开放状态。如果将所有内容收集到一个字符串中,将其拆分为Vec<String>将是微不足道的,反之亦然。在我寻找解决方案的过程中,我很高兴能看到优雅、最新的Rust文件I/O代码。 - Jared
5
关于特征错误 (std::io::Read),请注意在 Rust 中您必须 显式 导入您希望使用的特征; 因此,在这里,您缺少了一个 use std::io::Read(它可以是 use std::io::{Read,BufReader} 来将两个导入合并在一起)。 - Matthieu M.
3个回答

417

我在这里展示的函数没有自己出现问题,但我使用 expect ,因为我不知道什么样的错误处理最适合您的应用程序。去读一下 The Rust Programming Language错误处理章节,以了解如何适当地处理自己程序中的故障。

Rust 1.26及以上版本

如果您不想关心底层细节,则有一行函数可用于读写操作。

将文件读取为String

use std::fs;

fn main() {
    let data = fs::read_to_string("/etc/hosts").expect("Unable to read file");
    println!("{}", data);
}

将文件读取为Vec<u8>

use std::fs;

fn main() {
    let data = fs::read("/etc/hosts").expect("Unable to read file");
    println!("{}", data.len());
}

编写文件

use std::fs;

fn main() {
    let data = "Some data!";
    fs::write("/tmp/foo", data).expect("Unable to write file");
}

Rust 1.0 及以后版本

这些形式比为您分配 StringVec 的一行函数稍微冗长,但更强大,因为您可以重用已分配的数据或附加到现有对象。

读取数据

读取文件需要两个核心组件:FileRead

将文件读取为String

use std::fs::File;
use std::io::Read;

fn main() {
    let mut data = String::new();
    let mut f = File::open("/etc/hosts").expect("Unable to open file");
    f.read_to_string(&mut data).expect("Unable to read string");
    println!("{}", data);
}

将文件读取为Vec<u8>类型

use std::fs::File;
use std::io::Read;

fn main() {
    let mut data = Vec::new();
    let mut f = File::open("/etc/hosts").expect("Unable to open file");
    f.read_to_end(&mut data).expect("Unable to read data");
    println!("{}", data.len());
}

写入文件

写入文件类似,只是我们使用Write trait并始终写出字节。你可以使用as_bytesString / &str转换为字节:

use std::fs::File;
use std::io::Write;

fn main() {
    let data = "Some data!";
    let mut f = File::create("/tmp/foo").expect("Unable to create file");
    f.write_all(data.as_bytes()).expect("Unable to write data");
}

缓冲I/O

我感到社区对使用BufReaderBufWriter而不是直接从文件中读取有些推崇。

缓冲读取器(或写入器)使用缓冲区来减少 I/O 请求的数量。例如,一次访问磁盘以读取 256 字节要比访问磁盘 256 次更有效率。

话虽如此,当读取整个文件时,我不认为使用缓冲读取器/写入器会很有用。read_to_end似乎会通过大块复制数据,所以传输可能已经自然地合并为较少的 I/O 请求。

以下是一个使用它进行读取的示例:

use std::fs::File;
use std::io::{BufReader, Read};

fn main() {
    let mut data = String::new();
    let f = File::open("/etc/hosts").expect("Unable to open file");
    let mut br = BufReader::new(f);
    br.read_to_string(&mut data).expect("Unable to read string");
    println!("{}", data);
}

对于写作:

use std::fs::File;
use std::io::{BufWriter, Write};

fn main() {
    let data = "Some data!";
    let f = File::create("/tmp/foo").expect("Unable to create file");
    let mut f = BufWriter::new(f);
    f.write_all(data.as_bytes()).expect("Unable to write data");
}

BufReader 更适合逐行读取:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() {
    let f = File::open("/etc/hosts").expect("Unable to open file");
    let f = BufReader::new(f);

    for line in f.lines() {
        let line = line.expect("Unable to read line");
        println!("Line: {}", line);
    }
}

3
我没有太多的依据,但在研究过程中,我感受到社区对于使用BufReader和BufWriter而不是直接从文件读取字符串有一些推动力。您了解这些对象或使用它们与您在答案中展示的“更经典”的版本相比的利弊吗? - Jared
2
缓冲写入器(bufwriter)需要在任何时候显式刷新吗? - Dragoon
1
@Dragoon 这取决于你的使用情况。文档中说:BufWriter<W> 被丢弃之前调用 flush 是至关重要的。尽管丢弃会尝试刷新缓冲区的内容,但在丢弃过程中发生的任何错误都将被忽略。调用 flush 确保缓冲区为空,因此丢弃操作甚至不会尝试文件操作。 - Shepmaster
1
我想补充一下,当你读取文件时,它将相对于执行cargo run命令的位置。因此,如果你有一个名为src/map.json的文件,并在crate的根目录下运行cargo run,你需要执行以下操作:fs::read_to_string("./src/map.json").expect("unable to open file"); - Jose A
1
@BallpointBen io::copy 用于从读取器传输到写入器。任何转换都需要建模为读取器适配器或写入器适配器。 - Shepmaster
显示剩余5条评论

26

如果你要写入文件,接受的答案很好。但是如果你需要追加内容到文件中,你必须使用OpenOptions结构体:

use std::io::Write;
use std::fs::OpenOptions;

fn main() {
    let data = "Some data!\n";
    let mut f = OpenOptions::new()
        .append(true)
        .create(true) // Optionally create the file if it doesn't already exist
        .open("/tmp/foo")
        .expect("Unable to open file");
    f.write_all(data.as_bytes()).expect("Unable to write data");
}

缓冲写入仍然以相同的方式工作:

use std::io::{BufWriter, Write};
use std::fs::OpenOptions;

fn main() {
    let data = "Some data!\n";
    let f = OpenOptions::new()
        .append(true)
        .open("/tmp/foo")
        .expect("Unable to open file");
    let mut f = BufWriter::new(f);
    f.write_all(data.as_bytes()).expect("Unable to write data");
}

2
我发现在上面的流程中,我需要导入 std::io::Write - Doug Bradshaw

11

通过使用缓冲I/O,您可以复制大于实际内存的文件大小。


use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Write, BufRead};

fn main() {
    let read = File::open(r#"E:\1.xls"#);

    let write = OpenOptions::new().write(true).create(true).open(r#"E:\2.xls"#);

    let mut reader = BufReader::new(read.unwrap());

    let mut writer = BufWriter::new(write.unwrap());

    let mut length = 1;

    while length > 0 {
        let buffer = reader.fill_buf().unwrap();

        writer.write(buffer);

        length = buffer.len();
        reader.consume(length);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接