rust学习之文件操作概述

rust May 18, 2021

前言

rust的文件操作属于标准库,在std::fs包下面。下面的东西很多,我一下也没办法都研究清楚,所以写一个简单的,把基础的文件操作总结下。

文件的生命周期

对于文件系统来说,在windos和Linux下面的原生api是不同的。但是一般高级的编程语言会做自己的抽象,将两个平台的api统一。rust提供了统一的抽象api,也提供了每个平台底层的api。这里我们介绍的都是rust提供的抽象api。
一个文件在创建后,经历的生命周期一般如下图所示,我们后面的介绍也是根据这个图。

20210518145803

create

创建文件,目前我所知道的方法是std::fs::File::create(),源码如下。
[me]: 其实从源码看,还可以用OpenOptions来实现。创建操作其实包含在打开的概念里面的。


    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn create<P: AsRef<Path>>(path: P) -> io::Result<File> {
        OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref())
    }

例子:

use std::fs;
use std::fs::File;

fn main() {
    let result = File::create("./test.txt");
    println!("{:?}", result.unwrap())
}

这里看源码的时候,我有一个疑惑,create函数的入参是AsRef<Path>的trait,但是example里面都是直接用的string入参,后来在string的源码里面可以发现string和str类型都是实现了AsRef<Path>这个trait。
所以目前可以通过三种方式入参:

  1. string
  2. &str
  3. path strcut

[me]:这里感觉就设计的很巧妙,如果在java里面实现类似的效果,我们首先想到的是都是方法重载吧,不过这样要多写很多代码。但是在rust里面非常灵活,只需要让三个类型去实现AsRef<Path>的trait。

open

File::open()函数,打开一个存在的文件,默认是只读模式。返回结果是一个Eesutl,如果文件不存在,Err是no such file or directory。源码如下:


    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<File> {
        OpenOptions::new().read(true).open(path.as_ref())
    }

例子:


use std::fs;
use std::fs::File;

fn main() {

    let result = File::open("./one.txt");
    println!("{:?}", result.unwrap())
}


看源码底层操作还是用的OpenOptions,所以如果我们加了一个参数,就可以改为不存在文件的时候创建。

ps:如果想创建文件必须设置write或append为true。


    let result1 = OpenOptions::new().write(true).create(true).open("./one.txt");

    println!("{:?}", result1.unwrap());

copy

复制文件的原理是byte级的复制。如果原文件不存在Result是文件不存在的err。如果目标文件不存在会直接创建。目标文件存在会被直接覆盖。成功的Result是成功拷贝的byte size。源码如下

#[stable(feature = "rust1", since = "1.0.0")]
pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
    fs_imp::copy(from.as_ref(), to.as_ref())
}

例子:


use std::fs;
use std::fs::{File, OpenOptions};

fn main() {
    let result = fs::copy("hello.txt", "111.txt");
    println!("{:?}", result.unwrap());
}

rename

重新命名一个文件。如果文件不存在或者用户权限不够会导致失败。如果重命名文件存在会直接替换。源码如下:

#[stable(feature = "rust1", since = "1.0.0")]
pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
    fs_imp::rename(from.as_ref(), to.as_ref())
}

例子:

use std::fs;
use std::fs::{File, OpenOptions};

fn main() {
    let result = fs::rename("hello.txt", "hello_world.txt");
    println!("{:?}", result.unwrap())
}


read

读取文件的字符。有两个方法一个是直接读取文件,然后返回vec的字符。一个是经典的传一个数组进去,然后填充数组的方式。

[me]:不展示源码了,源码太底层我也没看懂。

例子:


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

fn main() {

    let result1 = fs::read("./111.txt");

    println!("{:?}", result1.unwrap());

    let result= OpenOptions::new().read(true).write(true).open("./111.txt");
    let mut temp: [u8; 100] = [0; 100];
    let result2 = result.unwrap().read(&mut temp);

    println!("{:?}", temp)
}

read_to_string

升级的方式,简单好用。读取文件并且返回一个string类型。还有一个方法,不太好用...

例子:


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

fn main() {
    let result = fs::read_to_string("./111.txt");

    let result1 = OpenOptions::new().read(true).write(true).open("./111.txt");
  
    let mut temp = String::new();
    let result2 = result1.unwrap().read_to_string(&mut temp);

    println!("{}", result.unwrap());

    println!("{}", temp);

}

write

fs::write 打开一个文件,并且覆盖写内容。也就是说不能追加内容,另外如果文件不存在直接创建。这个函数的好处是参数是string,使用起来方便。另外可以通过file struct的write方法写,还可以追加写,但是参数是u8 array。使用起来不太方便。

例子:


use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};

fn main() {

    let result = fs::write("./111.txt", "hello write");

}

query

这里的query是根据文件的信息(metadata)查找的意思,并没有专门的api。文件的metadata主要有,大小(len),是否是目录(is_dir),权限,是否可以更改等等。

[me]:也可以通过file struct 获取metadata。


use std::fs;

fn main() {
    let file_metadata = fs::metadata("111.txt").unwrap();
    println!("Len: {}, last accessed: {:?}, modified : {:?}, created: {:?}", file_metadata.len(), file_metadata.accessed(), file_metadata.modified(), file_metadata.created());
    println!("Is file: {}, Is dir: {}, is Symlink: {}", file_metadata.is_file(),
             file_metadata.is_dir(), file_metadata.file_type().is_symlink());
    println!("Permissions of file are: {:?}", file_metadata.permissions());
}


close

得益于所有权机制,rust不用我们显示调用close方法。file struct在离开自己的作用域的时候,会自动把自己close掉^_^。

总结

总结下文件操作有三种思路:

  1. fs包下面的函数操作。最全面的,最方便。
  2. file struct里面的函数和方法。不全面,不好用。
  3. 通过OpenOptions获取file struct操作。最全面,不方便,但是最精细的控制。

这下可以用rust来操作文件了,但是还不够。因为文件系统还有dir和path两个大魔头要打败才能掌握。