Rust 中讀取文件的多種方式

這一次,我想分享一下我在 Rust 中使用不同方法讀取文件的經驗,每種方法都有自己的優缺點。

讀取 ASCII 文件

基本上,在 Rust 中有三種讀取 ASCII 文件的方法,還有一種使用系統調用的方法,可能會產生危害,不推薦使用。

在一個字符串中加載整個文件

這是通過 std::fs::read_to_string() 方法完成的。如果您熟悉 Python 或 Ruby,那麼這個方法與 Python 的 read() 函數或 Ruby 的 File.read() 方法一樣方便。結合泛型的強大功能,可以輕鬆構建一個 struct vector,匹配文件中的數據類型:

// Loads an entire file of ip addresses as a Vector of Result<Ipv4Addr> structs
fn read_all<T: FromStr>(file_name: &str) -> Vec<Result<T, <T as FromStr>::Err>> {
    std::fs::read_to_string(file_name)
        .expect("file not found!")
        .lines()
        .map(|x| x.parse())
        .collect()
}
let addr = read_all::<Ipv4Addr>("ipv4.txt");

使用 lines() 迭代器

這是使用 lines() 迭代器逐行讀取文件的另一種簡單方法。該迭代器作用於 File 對象創建的 BufReader。因此需要創建一個 BufReader 結構來使用它。

這個示例函數在每一行調用一個閉包:

// Calls *func()* on each line
fn read_iter(file_name: &str, func: fn(&str)) {
    let file = File::open(file_name).expect("file not found!");
    let reader = BufReader::new(file);
    for line in reader.lines() {
        func(&line.unwrap());
    }
}

這個方法對於小文件很有用,但不適用於非常大的文件,因爲每次迭代都會導致一個 String::new() 內存分配。

使用 read_line() 函數

read_line() 函數可以使用相同的 String 緩衝區,而無需在每次迭代中重新分配。但是由於 Rust 迭代器的工作方式,我們不能在這裏構建一個標準的迭代器。我們必須使用一個簡單的循環結構,並在 read_line() 函數返回 Ok(0) 時停止它,也就是 EOF:

// Reuse the same String buffer
fn read_line(file_name: &str, func: fn(&str)) -> Result<(), std::io::Error> {
    // open target file
    let file = File::open(&file_name)?;
    // uses a reader buffer
    let mut reader = BufReader::new(file);
    let mut line = String::new();
    loop {
        match reader.read_line(&mut line) {
            Ok(bytes_read) => {
                // EOF: save last file address to restart from this address for next run
                if bytes_read == 0 {
                    break;
                }
                func(&line);
                // do not accumulate data
                line.clear();
            }
            Err(err) => {
                return Err(err);
            }
        };
    }
    Ok(())
}

不要忘記在獲得數據後清除緩衝區,否則緩衝區會意外增長。

還可以使用 split() 迭代器,它與 lines() 有相同的缺點:它每次迭代分配一個 Vec

// Reuse the same Vec<u8> buffer
fn read_split(file_name: &str, func: fn(&[u8])) -> Result<(), std::io::Error> {
    // open target file
    let file = File::open(&file_name)?;
    // uses a reader buffer
    let reader = BufReader::new(file);
    // use a for loop construct
    for line in reader.split(0x10) {
        func(&line?);
    }
    Ok(())
}

使用 mmap () api

使用 mmap() 系統調用,它不包含在標準的 Rust 庫中,你可以使用 memmap crate:

// Maps the file to memory
fn read_mmap(file_name: &str) -> Result<(), std::io::Error> {
    // open target file
    let file = File::open(&file_name)?;
    // create a memmap struct. After that, mmap variable maps directly file contents
    let mmap = unsafe { Mmap::map(&file)? };
    // use from_utf8() to convert to an UTF8 Rust string
    for s in mmap.split(|x| *x == 0x10) {
        println!("{:?}", std::str::from_utf8(&s).unwrap());
    };
    Ok(())
}

注意,如果文件被更改,你可能會得到一個 SIGBUS 錯誤。

讀取二進制文件

讀取二進制文件與讀取 ASCII 文件並沒有真正的區別。但是應該注意字節序問題,並使用 byteorder crate,儘管它與 Rust 讀取方法本身並沒有真正的關係。

這是一個讀取 PNG 文件頭的例子,以獲得圖像的寬度和高度:

// Read PNG file image width and height
fn read_png(file_name: &str) -> Result<(), std::io::Error> {
    const BUFFER_SIZE: usize = 256;
    // open target file
    let mut file = File::open(&file_name)?;
    // we'll use this buffer to get data
    let mut buffer = [0; BUFFER_SIZE];
    // reader PNG header of 8 bytes
    let _ = file.by_ref().take(8).read(&mut buffer)?;
    assert_eq!(&buffer[1..4], "PNG".as_bytes());
    // read IHDR chunk
    let chunk_size = file.read_u32::<BigEndian>().unwrap();
    let _ = file.by_ref().take(4).read(&mut buffer)?;
    assert_eq!(&buffer[0..4], "IHDR".as_bytes());
    let image_width = file.read_u32::<BigEndian>().unwrap();
    let image_height = file.read_u32::<BigEndian>().unwrap();
    println!("image is W={} x H={}", image_width, image_height);
    Ok(())
}

希望這些讀取文件的方式對你有所幫助。

本文翻譯自:

https://dev.to/dandyvica/different-ways-of-reading-files-in-rust-2n30

coding 到燈火闌珊 專注於技術分享,包括 Rust、Golang、分佈式架構、雲原生等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/SiaBUVKbsqBIBBeydUJlYA