改变循环中的两个相关值

Mutating two dependent values in loop

我尝试以一种足以满足我的目的的方式读取文件。我有一个文件 ID、名称和行索引(有序)的列表,对于每一对 (file_id, file_name, line_index) 我需要打开文件,按索引找到行并打印。

为了提高性能(我知道输入是有序的)我想缓存按行读取的 BufReader 并尽可能让文件保持打开状态。

fn main() {
    // positions in file
    // structure: (file_id, file_name, line_index_in_file)
    let positions = &vec![
        (1, String::from("file1"), 1), 
        (1, String::from("file1"), 2), 
        (1, String::from("file1"), 20), 
        (2, String::from("file2"), 15)];

    print_lines_from_file(&positions);
}

fn print_lines_from_file(found: &Vec<(i32, String, i32)>) {
    let mut last_file_id = -1;

    //let mut last_file_name = None;
    let mut open_file = None;
    let mut open_reader = None;

    for &(file_id, ref file_name, pos_in_file) in found {
        println!("{} {}", file_id, pos_in_file);

         if last_file_id < file_id {
            last_file_id = file_id;
            //last_file_name = file_ids.get(&file_id);

            if let Some(to_close) = open_file {
                drop(open_reader.unwrap());
                drop(to_close);
            }
            //let file = File::open(last_file_name.unwrap()).unwrap();
            let file = File::open(file_name).unwrap();
            open_file = Some(file);
            open_reader = Some(BufReader::new(&file));
        }

        // use reader to find the line in file and process
    }
}

我遇到了这个问题:

main.rs:40:48: 40:52 error: `file` does not live long enough
main.rs:40             open_reader = Some(BufReader::new(&file));

main.rs:40:48: 40:52 error: use of moved value: `file` [E0382]
main.rs:40             open_reader = Some(BufReader::new(&file));

很明显(file的生命周期真的很短),但我不知道如何解决它。 BufReader 依赖于 File,但是当 file_id 发生变化时,我需要稍后在循环中关闭 File

另外,我觉得在循环中以这种方式调用 drop 感觉不太舒服,因为在我看来我试图愚弄编译器。这个方法可以吗?

即使您知道更好的解决方案(例如如何通过 BufReader 关闭文件,我将不胜感激如何解决它的一般见解)。

您可以将 File 按值传递给 BufReader。这样你就只有一个拥有文件句柄的变量。您可以在 Option 上使用 take 将内部值移出它并留下 None 。这样你可以确保文件句柄在下一个文件被占用之前被释放(所以如果你重新打开同一个文件它不会恐慌)

let mut open_reader = None;

for &(file_id, ref file_name, pos_in_file) in found {
    println!("{} {}", file_id, pos_in_file);

     if last_file_id < file_id {
        last_file_id = file_id;
        //last_file_name = file_ids.get(&file_id);

        // take the value out of the `open_reader` to make sure that
        // the file is closed, so we don't panic if the next statement
        // tries to open the same file again.
        open_reader.take();
        //let file = File::open(last_file_name.unwrap()).unwrap();
        let file = File::open(file_name).unwrap();
        open_reader = Some(BufReader::new(file));
    }

    // use reader to find the line in file and process
}

您将文件的所有权授予 BufReader(这很明显,因为它是按值传递的),而不是借出它 - 现在 BufReader 的工作是关闭文件.掉落时,其拥有的File会依次掉落;所以你可以完全失去 open_file

编译器已成功阻止您破坏 BufReader 脚下的文件。

I'd like to cache the BufReader that reads by line and let the file stay open if possible.

最简单的方法是提前对数据进行分组:

use std::collections::HashMap;

fn print_lines_from_file(found: &[(i32, String, i32)]) {
    let mut index = HashMap::new();
    for line in found {
        let name = &line.1;
        index.entry(name).or_insert_with(Vec::new).push(line);
    }

    for (file_name, lines) in &index {
        let file = File::open(file_name).unwrap();

        for &&(file_id, _, line_index) in lines {
            // do something with `file`
            println!("processing ID {} ({}) line {}", file_id, file_name, line_index);
        }
    }
}

请注意,这使您不必为 file_id 设置特殊标记值(也可以使用 Option 来完成)。此外,即使您说数据已排序,这也允许您处理 file_id 未排序的情况。您还可以通过在矢量完成后对其进行排序来处理未排序 line_indexes 的情况。

此外:

  1. 你在 main 中有一个双重引用——你不需要说 &vec![...]
  2. 您应该接受 &[T] 而不是 &Vec<T>

恕我直言,一个更漂亮的解决方案是使用 itertools, specifically group_by_lazy:

extern crate itertools;

use itertools::Itertools;
use std::fs::File;
use std::io::BufReader;

fn main() {
    // structure: (file_id, file_name, line_index_in_file)
    let positions = [
        (1, String::from("file1"), 1),
        (1, String::from("file1"), 2),
        (1, String::from("file1"), 20),
        (2, String::from("file2"), 15)
    ];

    print_lines_from_file(&positions);
}

fn print_lines_from_file(found: &[(i32, String, i32)]) {
    for (filename, positions) in &found.iter().group_by_lazy(|pos| &pos.1) {
        println!("Opening file {}", filename);
        // let file = File::open(file_name).expect("Failed to open the file");
        // let file = BufReader::new(file);

        for &(id, _, line) in positions {
            println!("Processing ID {}, line {}", id, line);
        }
    }
}