如何两次访问BufReader?

5
尝试第二次访问变量sentences会导致value used here after move,我想了解如何在不引起此问题的情况下存储总数。 我尝试复制迭代器,但无法找到使其工作的方法或正确的方法。
extern crate regex;

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

fn main() {
    let sentences_path = Path::new("csv/testSentences.csv");
    let sentences = BufReader::new(File::open(&sentences_path).unwrap());

    let total = sentences.lines().count();

    for (index, sentence) in sentences.lines().enumerate() {
        let line = sentence.unwrap();
        println!("Processed {} of {}, {}", index, total, line);
    }

    println!("Total {}", total);
}

这个问题的两个答案都是权衡之间的选择:如果你没有很多内存或者文件非常大,那么读取两次是一个选择;或者如果你的文件很小并且有足够的内存,那么将整个文件存储在内存中也是一个选择。 - Stargateur
4个回答

6
如果所有权已经转移,您将无法访问该值。但是,您可以使用inspect检查您的行,而不改变内部行,只更新计数。
在通过迭代行找到count之后,您可以再次迭代它并按您所需的逻辑处理它。
您收到此编译器错误的主要原因是:count函数会消耗您使用的Lines,因此您无法再次访问您的变量,因为它已经被消耗。
以下是解决方案:
use std::fs::File;
use std::io::{BufRead, BufReader, Write};

fn main() {
    let path = "lines.txt";

    let mut output = File::create(path).unwrap();
    write!(output, "Rust\n\nFun").unwrap();

    let input = File::open(path).unwrap();
    let buffered = BufReader::new(input);

    let lines: Vec<_> = buffered.lines().collect();
    let total = lines.len();

    for (index, sentence) in lines.into_iter().enumerate() {
        let line = sentence.unwrap();
        println!("Processed {} of {}, {}", index, total, line);
    }

    println!("Total {}", total);
}

游乐场

使用这种方法,您无需两次读取文件。您可以将文件读入内存,然后仅迭代两次。


你需要提到这个解决方案的一个事实,即在收集行时所有文件都被放入内存中。 - Riccardo Casatta
1
文件被放入内存后更新。 - Akiner Alkan

3
你无法只用一个 BufReader 完成此任务,需要读取文件两次。第一次读取以计算总行数,第二次读取则用于处理每一行。因此你需要两个 BufReader
extern crate regex;                                                                                                     

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

fn get_file_reader(path: &Path) -> impl BufRead {
    BufReader::new(File::open(path).unwrap())
}

fn main() {
    let sentences_path = Path::new("csv/testSentences.csv");
    let sentences = get_file_reader(&sentences_path);

    let total = get_file_reader(&sentences_path).lines().count();

    for (index, sentence) in sentences.lines().enumerate() {
        let line = sentence.unwrap();
        println!("Processed {} of {}, {}", index, total, line);
    }

    println!("Total {}", total);
}

使用这种实现方式,您需要将整个文件读取两次到内存中,这不是内存高效的。 - Akiner Alkan
3
@Websterix中的"memory efficient"意为"内存使用效率高",这种解决方案比你将整个文件存储在内存中的方案使用更少的内存。 - Stargateur

0

使用 seek 或 rewind 函数将文件位置重置为起始位置。
请参阅以下链接:

seek 和 rewind
rust-by-example
BufRead
why-can-i-not-iterate-over-the-lines-of-a-file-after-counting-them

请注意,memmap2::Mmap 不需要倒回文件。

use std::error::Error;
use std::fs;
use std::io::{
    BufRead,
    BufReader,
    Seek,
    //SeekFrom,
};
use std::path::Path;
//use memmap2::Mmap;
//use rayon::prelude::*;

fn main() -> Result<(), Box<dyn Error>> {
    fs::write("test.txt", b"line1\nline2\nline3")?;
    let path = Path::new("test.txt");

    let (file, number_of_lines) = get_file(path)?;

    println!("number_of_lines: {number_of_lines}");

    for (index, line_result) in BufReader::new(&file).lines().enumerate() {
        println!("index {index}: {}", line_result?);
    }

    Ok(())
}

这样

/// File is an object providing access to an open file on the filesystem.
/// Use the seek or rewind functions to reset the position of the files to start.
fn get_file<P>(path: P) -> Result<(fs::File, usize), Box<dyn Error>>
where
    P: AsRef<Path> + std::marker::Copy + std::fmt::Debug,
{
    let mut file = match fs::OpenOptions::new()
        .read(true)
        .write(false)
        .create(false)
        .open(path)
    {
        Ok(file) => file,
        Err(error) => {
            panic!("Failed to open file {path:?}\n{error}");
        }
    };

    // Count function consumes the Lines
    let number_of_lines = BufReader::new(&file).lines().count();

    /*
    // https://docs.rs/memmap2/latest/memmap2/struct.Mmap.html
    let number_of_lines = unsafe { Mmap::map(&file)? }
        .lines()
        //.par_split(|&byte| byte == b'\n')
        .count();
    */

    if number_of_lines == 0 {
        panic!("The file {path:?} has no lines.");
    }

    // Reset files position to start
    // file.seek(SeekFrom::Start(0))?;
    file.rewind()?;

    Ok((file, number_of_lines))
}

Rust 操场

输出:

number_of_lines: 3
index 0: line1
index 1: line2
index 2: line3

-1

sentences.lines() 是一个迭代器。您可以通过调用 .by_ref() 来借用它而不是消耗它:

let mut lines = sentences.lines();
let total = lines.by_ref().count();
for (index, sentence) in lines.enumerate() {
    // ...
}

这根本行不通,by_ref() 不会克隆迭代器,它只会进行可变借用。 - Stargateur

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