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