rust文件操作随笔
数据类型
本文 仅使用 Rust 标准库,深入解析文件系统操作中涉及的所有核心数据类型,不引入任何外部 crate。
📚 目录
- rust文件操作随笔
- 📚 目录
- 1.
std::fs
模块概览 - 2.
Path
与PathBuf
- 3.
DirEntry
- 4.
Metadata
- 5.
FileType
- 6.
Permissions
- 7.
File
- 8.
OpenOptions
- 9.
Read
/Write
/Seek
Traits - 10.
BufReader
/BufWriter
- 11.
Stdio
相关类型 - 12.
std::io::Result
与ErrorKind
- ✅ 总结:标准库文件类型全景图
1. std::fs
模块概览
std::fs
是 Rust 标准库中用于文件系统操作的模块,提供跨平台的文件、目录、元数据等操作。
use std::fs;
它本身不定义新类型,而是提供函数和 re-export 以下类型:
File
OpenOptions
Metadata
Permissions
DirEntry
FileType
2. Path
与 PathBuf
✅ 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::Result
与 ErrorKind
✅ 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::fs
、std::io
、std::path
中的每一个函数和方法进行 超详细、超深入、超实用 的讲解,包含完整示例、边界情况、错误处理和最佳实践。
📚 目录
fs::read
—— 二进制文件读取fs::read_to_string
—— 文本文件读取fs::write
—— 文件写入File::open
/File::create
—— 文件打开OpenOptions
—— 灵活文件打开fs::create_dir
/create_dir_all
—— 目录创建fs::read_dir
—— 目录遍历fs::remove_file
/remove_dir
/remove_dir_all
—— 删除操作fs::rename
—— 重命名与移动fs::copy
—— 文件复制fs::hard_link
/symlink
—— 链接操作fs::metadata
/symlink_metadata
—— 元数据获取fs::set_permissions
—— 权限设置(Unix)fs::canonicalize
—— 路径规范化File
方法详解:sync_all
,sync_data
,metadata
BufReader
/BufWriter
方法详解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::open
和 File::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 |
---|---|---|
移动/重命名 | ✅ | ❌ |
复制 | ❌ | ✅ |
原子性 | 同一文件系统内是 | 否 |
目录 | ✅ | ❌ |
11. fs::hard_link
/ symlink
—— 链接操作
✅ 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!("是符号链接");
}
12. fs::metadata
/ symlink_metadata
—— 元数据获取
✅ fs::metadata<P: AsRef<Path>>(path: P) -> Result<Metadata>
- 获取文件/目录元数据。
- 跟随符号链接。
let meta = fs::metadata("file.txt")?;
println!("大小: {} 字节", meta.len());
✅ fs::symlink_metadata<P: AsRef<Path>>(path: P) -> Result<Metadata>
- 获取符号链接本身的元数据。
- 不跟随。
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 文件操作参考手册。