quinnwencn / blog

Apache License 2.0
0 stars 0 forks source link

[RustLearning] 文件IO操作 #33

Open quinnwencn opened 1 month ago

quinnwencn commented 1 month ago

Rust如何讀取文件、寫入文件、追加文件?能不能像C或者C++一樣使用mmap?

quinnwencn commented 1 month ago

Rust中常用於文件IO操作的一般是標準庫的fs和io,最多再加上一個標準庫的path。這三個庫提供的功能已經基本滿足常見的IO需求,現在我們依次看如何讀文件、謝文件、追加文件以及同時讀寫文件。

讀文件

use std::fs::File;
use std::io::Read;

fn main() {
    let mut file = File::open("./auth.json").expect("Unable to open file");
    let mut buffer = String::new();

    file.read_to_string(&mut buffer).expect("Unable to read file");
    println!("{}", buffer);
}

上面這個例子,使用了標準庫std::fs::File打開了一個文件,然後用read_to_string讀取文件的內容。這裏雖然讀文件不對文件進行更改,但是還是要講file聲明為mut類型,否則read_to_string無法通過編譯。如果file要聲明為只讀的,那麼可以通過std::io::BufReader來實現:

use std::fs::File;
use std::io::{Read, BufReader};

fn main() {
    let file = File::open("./auth.json").expect("Unable to open file");
    let mut buffer = String::new();

    let mut buf_reader = BufReader::new(&file);
    buf_reader.read_to_string(&mut buffer).expect("Unable to read file");
    println!("{}", buffer);
}

BufReader是std::io提供的一個緩衝區功能,相比於直接從文件讀取,緩衝區讀取方式減少了系統調用的次數,提高了文件讀取的性能,尤其是在大文件讀取時,性能更好。但是,BufReader並不是一次性將文件的內容映射或者加載到內存,而是按需讀取,對於大文件讀取,仍然導致內存消耗較高。BufReader適合小和中等文件大小的讀取處理,如果是超大文件,那麼還是建議使用mmap讀取,這個下文會講解。

寫入文件

use std::fs::File;
use std::io::Write;

fn main() {
    let mut file = File::create("write.log").expect("Unable to create file");
    let content = "Hello world!";
    file.write_all(content.as_bytes()).expect("Unable to write file");

    file.flush().expect("Unable to flush file");
}

寫文件時,打開文件使用的是create。當然,也可以使用BufWriter來操作,提升性能:

use std::fs::File;
use std::io::{Write, BufWriter};

fn main() {
    let file = File::create("write.log").expect("Unable to create file");
    let mut buffer = BufWriter::new(file);
    let content = "Hello world1!";
    buffer.write_all(content.as_bytes()).expect("Unable to write file");

    buffer.flush().expect("Unable to flush file");
} 

追加文件

從讀取文件和寫入文件可以看出,直接使用File的open或者create方法,無法像C語言一樣,在打開或者創建的時候指定文件的一些標識,例如truncate, append等,因此也就無法在寫文件中追加文件,每次打開都會截斷原文件,重新寫入。Rust在fs中還提供了OpenOptions的包,可以在打開文件時指定打開標識,甚至設置權限。

use std::fs::OpenOptions;
use std::io::{Write, BufWriter};

fn main() {
    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("write.log")
        .expect("Unable to open write.log");
    let mut buffer = BufWriter::new(file);
    let content = "Hello world!";
    buffer.write_all(content.as_bytes()).expect("Unable to write file");

    buffer.flush().expect("Unable to flush file");
} 

通過append方法,我們指定打開文件的模式為追加,因此不會覆蓋原有內容,直接在原有內容後追加。除此之外,OpenOptions還支持創建文件時指定權限:

use std::fs::OpenOptions;
use std::io::{Write,BufWriter};
use std::os::unix::fs::OpenOptionsExt;

fn main() {
    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .mode(0o644)
        .open("write.log")
        .expect("Unable to open write.log");
    let mut buffer = BufWriter::new(file);
    let content = "Hello world!";
    buffer.write_all(content.as_bytes()).expect("Unable to write file");

    buffer.flush().expect("Unable to flush file");
} 

指定權限需要使用use std::os::unix::fs::OpenOptionsExt;

mmap操作

mmap的操作在Rust中已經有庫支持了,據我所知,現在支持的有mmap和memmap2兩個庫。這兩個庫的底層實現不一樣,mmap使用nix庫與操作系統交互,而memmap2使用libc和winapi庫和操作系統打交道,這裏我以memmap2為例子舉例:

use std::fs::OpenOptions;
use memmap2::MmapMut;

fn main() -> std::io::Result<()> {
    // 打開文件,這裡假設文件名為 "example.txt"
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .append(true)
        .open("example.txt")?;

    // 將文件大小設置為 100 字節
    file.set_len(100)?;

    // 創建一個可變的內存映射
    let mut mmap = unsafe { MmapMut::map_mut(&file)? };

    // 向內存映射中寫入數據
    mmap[0..6].copy_from_slice(b"Hello!");

    // 確保內存中的更改寫回到文件
    mmap.flush()?;

    // 讀取內存映射中的數據
    let content = &mmap[0..6];
    println!("{}", std::str::from_utf8(content).expect("Invalid UTF-8"));

    Ok(())
}

這裏需要在Cargo.toml中添加使用memmap2:

[denpendencies]
memmap2 = "0.5"