rust文件操作

2025-09-24

rust文件操作随笔

数据类型

本文 仅使用 Rust 标准库,深入解析文件系统操作中涉及的所有核心数据类型,不引入任何外部 crate。


📚 目录


1. std::fs 模块概览

std::fs 是 Rust 标准库中用于文件系统操作的模块,提供跨平台的文件、目录、元数据等操作。

use std::fs;

它本身不定义新类型,而是提供函数和 re-export 以下类型:

  • File
  • OpenOptions
  • Metadata
  • Permissions
  • DirEntry
  • FileType

2. PathPathBuf

Path —— 不可变路径引用

  • 类型:&Path
  • 作用:表示一个文件系统路径(不拥有数据)
  • 类比:&str 之于 String
use std::path::Path;

let path: &Path = Path::new("./data/config.json");

PathBuf —— 可变路径(拥有所有权)

  • 类型:PathBuf
  • 作用:拥有路径数据,可修改
  • 类比:String 之于 &str
use std::path::PathBuf;

let mut buf: PathBuf = PathBuf::from("./data");
buf.push("logs"); // buf 现在是 "./data/logs"

✅ 创建方式

方式 示例
Path::new(str) Path::new("a/b/c")
PathBuf::from(str) PathBuf::from("a/b/c")
format!().into() format!("{}/{}", dir, file).into()

✅ 核心方法

方法 说明
.parent() 返回父目录(Option<&Path>
.file_name() 返回文件名(Option<&OsStr>
.file_stem() 返回主文件名(不含扩展名)
.extension() 返回扩展名(Option<&OsStr>
.join(other) 拼接路径,返回 PathBuf
.with_file_name(name) 替换文件名,返回 PathBuf
.with_extension(ext) 替换扩展名,返回 PathBuf
.is_absolute() 是否绝对路径
.is_relative() 是否相对路径
.has_root() 是否有根(如 /C:\

✅ 跨平台说明

  • Path 会自动处理 /\ 分隔符。
  • 推荐在代码中使用 /,Rust 会自动转换。

3. DirEntry

表示目录遍历中的一个条目。

use std::fs::read_dir;

for entry in read_dir(".")? {
    let entry: std::io::Result<std::fs::DirEntry> = entry;
    let entry = entry?; // 解包
    // 使用 entry...
}

✅ 核心方法

方法 返回类型 说明
.path() PathBuf 完整路径
.file_name() OsString 文件名(无路径)
.metadata() Result<Metadata> 获取元数据(会系统调用)
.file_type() Result<FileType> 获取文件类型(更快)
.ino() u64 inode 编号(Unix)

⚠️ .metadata().file_type() 都返回 Result,可能失败。


4. Metadata

文件或目录的元数据,通过 fs::metadata(path)entry.metadata() 获取。

let meta = fs::metadata("hello.txt")?;

✅ 核心方法

方法 返回类型 说明
.is_dir() bool 是否是目录
.is_file() bool 是否是文件
.len() u64 文件大小(字节)
.permissions() Permissions 权限信息
.modified() Result<SystemTime> 最后修改时间
.created() Result<SystemTime> 创建时间(平台相关)
.accessed() Result<SystemTime> 最后访问时间

📌 提示:metadata() 会跟随符号链接。
使用 fs::symlink_metadata() 获取符号链接本身的元数据。


5. FileType

轻量级文件类型标识,比 Metadata 更快。

let file_type = entry.file_type()?;

✅ 核心方法

方法 说明
.is_dir() 是否是目录
.is_file() 是否是文件
.is_symlink() 是否是符号链接

✅ 推荐在遍历目录时使用 .file_type() 判断类型,性能更好。


6. Permissions

文件权限信息。

let perm = fs::metadata("script.sh")?.permissions();

✅ 修改权限(仅 Unix)

#[cfg(unix)]
{
    use std::os::unix::fs::PermissionsExt;
    let mut perm = fs::metadata("script.sh")?.permissions();
    PermissionsExt::set_mode(&mut perm, 0o755); // chmod +x
    fs::set_permissions("script.sh", perm)?;
}

⚠️ Windows 权限模型不同,标准库不提供细粒度控制。


7. File

表示一个打开的文件句柄。

use std::fs::File;

let file = File::open("read.txt")?;      // 只读打开
let file = File::create("write.txt")?;   // 写入(覆盖)

✅ 实现的 Trait

  • Read:可读
  • Write:可写
  • Seek:可定位
  • Drop:自动关闭(RAII)

8. OpenOptions

用于灵活配置文件打开方式。

use std::fs::OpenOptions;

let file = OpenOptions::new()
    .read(true)
    .write(true)
    .create(true)      // 不存在则创建
    .append(true)      // 追加模式
    .open("log.txt")?;

✅ 常用配置

方法 说明
.read(bool) 是否可读
.write(bool) 是否可写
.append(bool) 追加模式(写入到末尾)
.truncate(bool) 是否清空文件(默认 true)
.create(bool) 不存在时创建
.create_new(bool) 创建新文件(已存在则失败)

9. Read / Write / Seek Traits

这三个是 I/O 的核心 trait,定义在 std::io

Read

所有可读类型实现,如 File, TcpStream, &[u8]

use std::io::Read;

let mut buffer = [0; 1024];
file.read(&mut buffer)?; // 读取数据

常用方法

  • .read(&mut buf)Result<usize>
  • .read_to_end(&mut vec)Result<usize>
  • .read_to_string(&mut string)Result<usize>

Write

所有可写类型实现。

use std::io::Write;

file.write_all(b"Hello")?; // 写入所有数据
file.flush()?;             // 刷新缓冲区

常用方法

  • .write(&buf)Result<usize>
  • .write_all(&buf)Result<()>(确保全部写入)
  • .flush()Result<()>

Seek

用于在文件中定位。

use std::io::SeekFrom;

file.seek(SeekFrom::Start(100))?; // 跳到第 100 字节
file.seek(SeekFrom::End(-10))?;   // 从末尾倒数第 10 字节

10. BufReader / BufWriter

提供缓冲 I/O,减少系统调用,提升性能。

use std::io::{BufReader, BufWriter};

let file = File::open("huge.txt")?;
let mut reader = BufReader::new(file);

let file = File::create("output.txt")?;
let mut writer = BufWriter::new(file);

✅ 优势

  • 读取时:一次系统调用读取大块数据,后续从内存缓冲读取。
  • 写入时:数据先写入缓冲,缓冲满或调用 .flush() 时才写入磁盘。

11. Stdio 相关类型

用于标准输入/输出/错误。

use std::io::{stdin, stdout, stderr, Stdin, Stdout, Stderr};

let stdin: Stdin = stdin();
let stdout: Stdout = stdout();
let stderr: Stderr = stderr();

✅ 使用示例

use std::io::Write;

writeln!(stdout(), "正常输出")?;
writeln!(stderr(), "错误输出")?;

12. std::io::ResultErrorKind

std::io::Result<T>

Result<T, std::io::Error> 的类型别名。

type Result<T> = std::result::Result<T, std::io::Error>;

所有 fs 函数返回此类型。

ErrorKind

表示错误的类别,用于模式匹配。

use std::io::{Error, ErrorKind};

match fs::read_to_string("config.json") {
    Ok(content) => println!("{}", content),
    Err(e) => match e.kind() {
        ErrorKind::NotFound => println!("文件不存在"),
        ErrorKind::PermissionDenied => println!("无权限"),
        _ => return Err(e),
    },
}

✅ 常见 ErrorKind

类型 说明
NotFound 文件/目录不存在
PermissionDenied 无权限
AlreadyExists 文件已存在
InvalidData 数据无效(如非 UTF-8)
BrokenPipe 管道断开
WouldBlock 操作会阻塞(非阻塞 I/O)

✅ 总结:标准库文件类型全景图

类型 用途 所在模块
Path / PathBuf 路径表示 std::path
DirEntry 目录条目 std::fs
Metadata 文件元数据 std::fs
FileType 文件类型 std::fs
Permissions 权限 std::fs
File 文件句柄 std::fs
OpenOptions 打开配置 std::fs
Read / Write / Seek I/O trait std::io
BufReader / BufWriter 缓冲 I/O std::io
Stdin / Stdout / Stderr 标准流 std::io
Result / Error / ErrorKind 错误处理 std::io

🎯 核心思想

Rust 标准库通过 组合 这些类型和 trait,实现了:

  • 安全性Result 强制错误处理)
  • 灵活性OpenOptions, BufReader
  • 高效性(缓冲、轻量 FileType
  • 跨平台Path 自动处理分隔符)

🦀 Rust 标准库文件操作 —— 函数与方法超详细使用指南(仅 std

本文 仅使用 Rust 标准库,对 std::fsstd::iostd::path 中的每一个函数和方法进行 超详细、超深入、超实用 的讲解,包含完整示例、边界情况、错误处理和最佳实践。


📚 目录

  1. fs::read —— 二进制文件读取
  2. fs::read_to_string —— 文本文件读取
  3. fs::write —— 文件写入
  4. File::open / File::create —— 文件打开
  5. OpenOptions —— 灵活文件打开
  6. fs::create_dir / create_dir_all —— 目录创建
  7. fs::read_dir —— 目录遍历
  8. fs::remove_file / remove_dir / remove_dir_all —— 删除操作
  9. fs::rename —— 重命名与移动
  10. fs::copy —— 文件复制
  11. fs::hard_link / symlink —— 链接操作
  12. fs::metadata / symlink_metadata —— 元数据获取
  13. fs::set_permissions —— 权限设置(Unix)
  14. fs::canonicalize —— 路径规范化
  15. File 方法详解:sync_all, sync_data, metadata
  16. BufReader / BufWriter 方法详解
  17. Read / Write / Seek Trait 方法详解

1. fs::read —— 二进制文件读取

✅ 函数签名

pub fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>>

✅ 功能

  • 一次性读取整个文件为 Vec<u8>
  • 适用于 图片、音频、视频、二进制数据

✅ 使用示例

use std::fs;
use std::path::Path;

// 方式1:直接传字符串
let bytes = fs::read("data.bin")?;

// 方式2:传 Path
let path = Path::new("data.bin");
let bytes = fs::read(path)?;

// 方式3:传 PathBuf
let path_buf = PathBuf::from("data.bin");
let bytes = fs::read(path_buf)?;

✅ 返回值

  • 成功:Ok(Vec<u8>)
  • 失败:Err(std::io::Error)

✅ 错误处理

match fs::read("missing.txt") {
    Ok(bytes) => println!("读取 {} 字节", bytes.len()),
    Err(e) => match e.kind() {
        std::io::ErrorKind::NotFound => println!("文件不存在"),
        std::io::ErrorKind::PermissionDenied => println!("无权限"),
        _ => println!("其他错误: {}", e),
    },
}

✅ 边界情况

  • 空文件:返回 Ok(vec![])
  • 大文件:一次性加载到内存,可能耗尽内存(> GB 文件慎用)
  • 符号链接:跟随链接读取目标文件内容

✅ 最佳实践

  • 用于 小文件(< 10MB)
  • 大文件使用 BufReader 分块读取

2. fs::read_to_string —— 文本文件读取

✅ 函数签名

pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String>

✅ 功能

  • 一次性读取文件为 String
  • 要求文件是 UTF-8 编码

✅ 使用示例

let content = fs::read_to_string("hello.txt")?;
println!("{}", content);

✅ 错误处理

match fs::read_to_string("binary.bin") {
    Ok(text) => println!("{}", text),
    Err(e) => match e.kind() {
        std::io::ErrorKind::InvalidData => {
            println!("文件不是有效的 UTF-8 文本");
        }
        std::io::ErrorKind::NotFound => println!("文件不存在"),
        _ => println!("其他错误: {}", e),
    },
}

✅ 与 fs::read 的选择

场景 推荐函数
图片、音频、二进制 fs::read
文本(已知 UTF-8) fs::read_to_string
文本(编码不确定) fs::read + String::from_utf8_lossy()
let bytes = fs::read("unknown.txt")?;
let text = String::from_utf8_lossy(&bytes); // 安全转换

3. fs::write —— 文件写入

✅ 函数签名

pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>

✅ 功能

  • 覆盖写入 文件。
  • 自动创建文件(如果不存在)。
  • 自动创建目录(不自动,需先创建目录)。

✅ 使用示例

// 写入字符串
fs::write("log.txt", "Hello, World!\n")?;

// 写入字节切片
fs::write("data.bin", b"\x00\x01\x02")?;

// 写入 Vec<u8>
let data = vec![1, 2, 3];
fs::write("data.bin", &data)?;

✅ 覆盖行为

fs::write("file.txt", "第一次写入")?;
fs::write("file.txt", "第二次写入")?; // 第一次内容被覆盖

✅ 追加写入(标准库无直接函数)

use std::fs::OpenOptions;

let mut file = OpenOptions::new()
    .append(true)
    .create(true)
    .open("log.txt")?;
file.write_all(b"新日志\n")?;

✅ 错误处理

  • PermissionDenied:无写入权限
  • NotFound:父目录不存在(fs::write 不创建目录)
// 安全写入:先创建目录
fs::create_dir_all("logs")?;
fs::write("logs/app.log", "日志内容")?;

4. File::open / File::create —— 文件打开

File::open<P: AsRef<Path>>(path: P) -> Result<File>

  • 只读 打开文件。
  • 文件不存在 → NotFound
let file = File::open("read.txt")?;

File::create<P: AsRef<Path>>(path: P) -> Result<File>

  • 写入 打开文件。
  • 覆盖 原内容。
  • 自动创建文件。
let file = File::create("write.txt")?;

✅ 两者对比

行为 File::open File::create
文件不存在 失败 创建
文件存在 打开(不修改) 覆盖(清空)
模式 只读 可写(通常可读)

⚠️ File::create 创建的文件通常也可读,但不保证。


5. OpenOptions —— 灵活文件打开

✅ 使用场景

File::openFile::create 无法满足需求时,使用 OpenOptions

✅ 完整示例:追加模式

use std::fs::OpenOptions;

let file = OpenOptions::new()
    .read(true)          // 可读
    .write(true)         // 可写
    .append(true)        // 追加(写入到末尾)
    .create(true)        // 不存在则创建
    .open("log.txt")?;   // 返回 File

✅ 示例:只写模式(不读)

let file = OpenOptions::new()
    .write(true)
    .truncate(true)      // 清空
    .create(true)
    .open("output.txt")?;

✅ 示例:原子性创建新文件

let file = OpenOptions::new()
    .write(true)
    .create_new(true)    // 已存在则失败
    .open("temp.txt")?;  // 确保不会覆盖

✅ 方法链说明

方法 作用
.read(bool) 设置 O_RDONLY
.write(bool) 设置 O_WRONLY
.append(bool) 设置 O_APPEND
.truncate(bool) 设置 O_TRUNC(写入时清空)
.create(bool) 设置 O_CREAT
.create_new(bool) 设置 O_CREAT \| O_EXCL(原子创建)

6. fs::create_dir / create_dir_all —— 目录创建

fs::create_dir<P: AsRef<Path>>(path: P) -> Result<()>

  • 创建单层目录。
  • 父目录必须存在。
// 成功
fs::create_dir("logs")?;

// 失败:父目录 a 不存在
fs::create_dir("a/b/c")?; // Error: NotFound

fs::create_dir_all<P: AsRef<Path>>(path: P) -> Result<()>

  • 创建多层目录。
  • 推荐使用。
fs::create_dir_all("a/b/c")?; // 自动创建 a, a/b, a/b/c

✅ 安全性

  • 如果目录已存在,create_dir_all 返回 Ok(())不报错
  • create_dir 在目录存在时返回 ErrorKind::AlreadyExists
fs::create_dir_all("existing_dir")?; // OK
fs::create_dir("existing_dir")?;     // Error: AlreadyExists

7. fs::read_dir —— 目录遍历

✅ 函数签名

pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>

✅ 返回类型

  • ReadDir:一个迭代器,产生 Result<DirEntry>
  • 每个条目都可能出错(如权限不足)

✅ 完整示例

use std::fs;
use std::path::Path;

let path = Path::new(".");

for entry_result in fs::read_dir(path)? {
    let entry = entry_result?; // 处理 DirEntry 错误
    let file_name = entry.file_name(); // OsString
    let file_name_str = file_name.to_string_lossy(); // 转为 String
    let metadata = entry.metadata()?; // 获取元数据
    let file_size = metadata.len();

    println!("{:>8} {}", file_size, file_name_str);
}

✅ 性能优化:使用 file_type

// 推荐:使用 file_type(),不触发系统调用
for entry in fs::read_dir(".")? {
    let entry = entry?;
    if entry.file_type()?.is_dir() {
        println!("目录: {:?}", entry.file_name());
    }
}

// 不推荐:metadata() 触发系统调用
for entry in fs::read_dir(".")? {
    let entry = entry?;
    if entry.metadata()?.is_dir() { // 多一次系统调用
        println!("目录: {:?}", entry.file_name());
    }
}

8. fs::remove_file / remove_dir / remove_dir_all —— 删除操作

fs::remove_file<P: AsRef<Path>>(path: P) -> Result<()>

  • 删除文件
  • 不能删除目录。
fs::remove_file("temp.txt")?;

fs::remove_dir<P: AsRef<Path>>(path: P) -> Result<()>

  • 删除空目录
  • 目录非空 → ErrorKind::DirectoryNotEmpty
fs::remove_dir("empty_dir")?;

fs::remove_dir_all<P: AsRef<Path>>(path: P) -> Result<()>

  • 递归删除 目录及其所有内容。
  • 非常危险,不可逆。
fs::remove_dir_all("temp_data")?; // 删除整个目录树

✅ 错误处理

match fs::remove_dir("non_empty") {
    Ok(()) => println!("删除空目录成功"),
    Err(e) => match e.kind() {
        std::io::ErrorKind::DirectoryNotEmpty => {
            println!("目录非空,无法删除");
        }
        std::io::ErrorKind::NotFound => println!("目录不存在"),
        _ => println!("其他错误: {}", e),
    },
}

9. fs::rename —— 重命名与移动

✅ 函数签名

pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()>

✅ 功能

  • 重命名文件或目录。
  • 移动文件或目录(同一文件系统内是原子操作)。

✅ 使用示例

// 重命名
fs::rename("old.txt", "new.txt")?;

// 移动
fs::rename("src/file.txt", "dst/file.txt")?;

// 移动目录
fs::rename("src/", "backup/")?;

✅ 原子性

  • 同一文件系统内rename 是原子操作(不会出现“只移动一半”)。
  • 跨文件系统:可能退化为“复制 + 删除”,非原子。

✅ 错误处理

  • AlreadyExists:目标已存在
  • NotFound:源不存在
  • PermissionDenied:无权限

10. fs::copy —— 文件复制

✅ 函数签名

pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64>

✅ 功能

  • 复制文件。
  • 返回复制的字节数
  • 自动创建目标文件。
  • 不复制目录

✅ 使用示例

let bytes_copied = fs::copy("source.txt", "backup.txt")?;
println!("复制了 {} 字节", bytes_copied);

✅ 权限保留(Unix)

  • 在 Unix 系统上,复制的文件保留原权限(如可执行位)。

✅ 与 rename 对比

操作 fs::rename fs::copy
移动/重命名
复制
原子性 同一文件系统内是
目录

fs::hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()>

  • 创建硬链接
  • 共享 inode。
  • 不能跨文件系统,不能链接目录。
fs::hard_link("original.txt", "link.txt")?;

fs::symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()>

  • 创建符号链接(软链接)。
  • 可以跨文件系统。
  • Windows 需要特殊权限。
fs::symlink("target.txt", "link.txt")?;

✅ 判断链接类型

let meta = fs::symlink_metadata("link.txt")?;
if meta.file_type().is_symlink() {
    println!("是符号链接");
}

fs::metadata<P: AsRef<Path>>(path: P) -> Result<Metadata>

  • 获取文件/目录元数据。
  • 跟随符号链接
let meta = fs::metadata("file.txt")?;
println!("大小: {} 字节", meta.len());
  • 获取符号链接本身的元数据。
  • 不跟随
let meta = fs::symlink_metadata("link.txt")?;
if meta.file_type().is_symlink() {
    println!("这是一个符号链接");
}

13. fs::set_permissions —— 权限设置(Unix)

✅ 仅限 Unix

#[cfg(unix)]
{
    use std::os::unix::fs::PermissionsExt;
    use std::fs;

    let mut perm = fs::metadata("script.sh")?.permissions();
    PermissionsExt::set_mode(&mut perm, 0o755); // rwxr-xr-x
    fs::set_permissions("script.sh", perm)?;
}

14. fs::canonicalize —— 路径规范化

let abs = fs::canonicalize("../dir/../file.txt")?;
// 返回绝对路径,解析 .. 和 .

❗ 路径必须存在,否则返回 NotFound


🎯 结语
这份指南覆盖了 Rust 标准库文件操作的所有函数与方法,从签名、用法、示例到错误处理和最佳实践,无所不包
收藏它,你将拥有一个 最全、最细、最实用 的 Rust 文件操作参考手册。