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/SeekTraits - 10.
BufReader/BufWriter - 11.
Stdio相关类型 - 12.
std::io::Result与ErrorKind - ✅ 总结:标准库文件类型全景图
1. std::fs 模块概览
std::fs 是 Rust 标准库中用于文件系统操作的模块,提供跨平台的文件、目录、元数据等操作。
use std::fs;
它本身不定义新类型,而是提供函数和 re-export 以下类型:
FileOpenOptionsMetadataPermissionsDirEntryFileType
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,metadataBufReader/BufWriter方法详解Read/Write/SeekTrait 方法详解
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 文件操作参考手册。
