bigywater.tech
Rust · 131

std::fs 文件读写

读写文件看起来很简单,细节却不少:文本与字节、错误的种类、写入时是覆盖还是截断、怎样写才不会让读者看到写了一半的文件。这篇用真实的文件系统与并发读者把这些都验证了。

0 对 8
原子替换与直接覆盖时,读者读到撕裂/不完整内容的次数
13 / 2 / 21
PermissionDenied、NotFound、IsADirectory 对应的 OS 错误码
InvalidData
read_to_string 遇到非法 UTF-8 的错误种类

一行读写

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    fs::write 之后:大小 20 字节
    fs::read_to_string: "第一行\n第二行\n"
    fs::read 得到 20 个字节,前 6 个 [231, 172, 172, 228, 184, 128](UTF-8,每个汉字 3 字节)
    写入非法 UTF-8 后 read_to_string: Err((InvalidData, "stream did not contain valid UTF-8"))
    同一个文件 fs::read: [255, 254, 65]

缓冲读写见BufReader 与缓冲读取和BufWriter 与批量写入。

io::Error

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    不存在:kind = NotFound  os_error = Some(2)  信息 = No such file or directory (os error 2)
    只读文件以写方式打开:kind = PermissionDenied  os_error = Some(13)
    create_new 遇到已存在的文件:kind = AlreadyExists
    把目录当文件读:kind = IsADirectory  os_error = Some(21)

OpenOptions

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    write(true) 只覆盖前 2 字节: "bbAAAAAA"
    write + truncate: "cc"
    append: "ccdd"
    create(true) 创建了空文件,大小 0
    File::create 会创建文件并截断已有内容:对已有内容的文件再 File::create →
      内容被清空,大小 0

原子替换

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    原子替换(tmp + rename)300 次期间,读者读到 完整的 A 1780 次、完整的 B 1525 次、撕裂的内容 0 次
    直接 File::create 覆盖 300 次期间,读者读了 308 次,其中读到被截断/不完整内容 8 次

文件描述符的释放见文件描述符与资源释放。结果的具体次数每次运行不同。

目录

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    read_dir 的返回顺序: ["z.txt", "m.txt", "a", "a.txt"](文档不保证顺序);排序后 ["a", "a.txt", "m.txt", "z.txt"]
    metadata(a): is_dir = true  is_file = false
    remove_dir 删除非空目录: Err(DirectoryNotEmpty)
    remove_dir_all 之后目录还在吗: false

路径类型见Path 与 PathBuf。

分步演示

std::fs
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。文件放在系统临时目录并在结束后删除(部分测试保留 u131 目录,不含敏感数据)
    • 没有验证:fsync 与掉电安全、rename 在不同文件系统间的行为、Windows 上 rename 覆盖已打开文件的行为、符号链接与硬链接、文件锁(见文件锁一篇)
    京ICP备2026067502号-1京公网安备11010502063487号