读取文件并获取字符串数组
Posted
技术标签:
【中文标题】读取文件并获取字符串数组【英文标题】:Read a file and get an array of strings 【发布时间】:2015-08-28 08:14:26 【问题描述】:我想读取一个文件并取回String
s 的向量。以下函数有效,但有没有更简洁或惯用的方式?
use std::fs::File;
use std::io::Read;
fn lines_from_file(filename: &str) -> Vec<String>
let mut file = match File::open(filename)
Ok(file) => file,
Err(_) => panic!("no such file"),
;
let mut file_contents = String::new();
file.read_to_string(&mut file_contents)
.ok()
.expect("failed to read!");
let lines: Vec<String> = file_contents.split("\n")
.map(|s: &str| s.to_string())
.collect();
lines
一些对我来说似乎不是最理想的事情:
两个单独的错误检查用于读取文件。 将整个文件读取到String
,该文件将被丢弃。如果我只想要前 N 行,这将特别浪费。
每行创建一个&str
,这将被丢弃,而不是以某种方式直接从文件到每行一个String
。
如何改进?
【问题讨论】:
使用lines()
迭代器:doc.rust-lang.org/std/io/trait.BufRead.html#method.lines
我不明白为什么这个问题被否决了。如果它被认为过于主观,我建议删除idiomatic
标签,因为它准确地描述了我要问的事情。
【参考方案1】:
作为BurntSushi said,您可以只使用the lines()
iterator。但是,按原样解决您的问题:
你应该读一下Error Handling in Rust;那些unwrap()
s 应该变成?
s,对于一些合理的E
,函数的结果变成Result<Vec<String>, E>
。在这里,我们重用了io::Result
类型别名。
使用lines()
迭代器。您可以做的另一件事是将整个文件读入String
并返回;有a lines()
iterator for strings as well。
这个你无能为力:file_contents
拥有它的内容,你不能将它们分成多个拥有的String
s。您唯一能做的就是借用每一行的内容,然后将其转换为新的String
。也就是说,您的表述方式暗示您认为创建&str
是昂贵的;不是。 字面意思只是计算一对偏移量并返回它们。 &str
切片实际上等效于 (*const u8, usize)
。
这是一个基本相同的修改版本:
use std::fs::File;
use std::io::self, BufRead;
use std::path::Path;
fn lines_from_file<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
我进行的另一项更改:filename
现在是通用的P: AsRef<Path>
,因为这是File::open
想要的,所以它可以接受更多类型而无需转换。
【讨论】:
整洁!但是返回 Result 是否意味着如果文件不可读,这将是调用者的问题?也许没有办法避免这种情况,同时又懒惰地阅读文件? @NathanLong 使用unwrap
或panic!
意味着如果文件不可读,则整个线程爆炸,调用者在没有警告的情况下死亡。如果调用者不关心这一点,他们可以在结果上调用unwrap
并获得相同的爆炸行为。 或者他们实际上可以决定如何处理错误。无论哪种方式,它都不会影响读取文件:两者都会导致函数以一种或另一种方式停止执行。
你能添加一个调用lines_from_file
的例子吗?【参考方案2】:
DK.'s answer 非常正确并且有很好的解释。但是,您说:
读取文件并获取字符串数组
Rust 数组有一个固定的长度,在编译时就知道了,所以我假设你的意思是“向量”。我会这样写:
use std::
fs::File,
io::prelude::*, BufReader,
path::Path,
;
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String>
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
// ---
fn main()
let lines = lines_from_file("/etc/hosts");
for line in lines
println!(":?", line);
-
与其他答案一样,使用为文件名实现
AsRef
的泛型类型是值得的。
Result::expect
缩短了 Err
的恐慌。
BufRead::lines
处理多种类型的换行符,而不仅仅是 "\n"
。
BufRead::lines
还为您提供单独分配的 String
s,而不是一大堆。
没有理由收集到临时变量只是为了返回它。尤其没有理由重复该类型 (Vec<String>
)。
如果您想在失败时返回 Result
,您可以根据需要将实现压缩为一行:
use std::
fs::File,
io::self, BufRead, BufReader,
path::Path,
;
fn lines_from_file(filename: impl AsRef<Path>) -> io::Result<Vec<String>>
BufReader::new(File::open(filename)?).lines().collect()
// ---
fn main()
let lines = lines_from_file("/etc/hosts").expect("Could not load lines");
for line in lines
println!(":?", line);
【讨论】:
你的'down to one line'函数似乎将整个文本作为1行返回给我,你的第一个工作。 @Blankman 我认为问题在于您没有处理从lines_from_file
返回的Result
。 Option
和 Result
实现 IntoIterator
,所以这可能会欺骗你。以上是关于读取文件并获取字符串数组的主要内容,如果未能解决你的问题,请参考以下文章