在循环中变换两个相关值

时间:2016-06-09 12:42:02

标签: rust ownership

我尝试以足够高效的方式阅读文件以达到我的目的。我有一个文件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更改后稍后关闭file_id

另外,我觉得在循环中以这种方式调用drop感觉不太舒服,因为它看起来像我试图欺骗编译器。那方法好吗?

请即使您知道更好的解决方案(例如,如何通过BufReader关闭文件,我也很感激如何解决这个问题。)

3 个答案:

答案 0 :(得分:3)

您可以将值File传递给BufReader。这样,您只拥有一个拥有文件句柄的变量。您可以使用take上的Option将内部值移出其中并留下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
}

答案 1 :(得分:2)

您已将该文件的所有权归属于BufReader(这是显而易见的,因为它是按价值传递的),而不是借给它 - 它现在是{{1}关闭文件的工作。当它被删除时,它所拥有的BufReader将被依次删除;所以你可以完全失去File

编译器成功阻止您在open_file脚下破坏文件。

答案 2 :(得分:2)

  

我想缓存逐行读取的BufReader,并尽可能让文件保持打开状态。

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

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_index es的情况。

此外:

  1. 您在main中有双重引用 - 您无需说&vec![...]
  2. 您应该接受&[T]而不是&Vec<T>
  3. 一个更美丽的解决方案,恕我直言,是使用itertools,特别是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);
            }
        }
    }