1.Path vs PathBuf 区别
| 类型 | 特性 | 适用场景 | 类比关系 |
|---|---|---|---|
Path |
不可变、无所有权(&Path) |
临时引用路径、只读操作 | &str(不可变字符串) |
PathBuf |
可变、有所有权 | 动态拼接、修改路径 | String(可变字符串) |
2 路径创建
2.1 Path
不可变,因为是引用类型,无法修改其指向的路径数据(返回
&Path对象)
2.1.1 Path::new创建
use std::path::Path;fn main() {let p: &Path = Path::new("/home/user");
}
2.2 PathBuf
可变(需用 mut 声明),可以修改路径内容(如追加、替换组件)
2.2.1 PathBuf::new创建
初始化一个空的
Vec<u8>,Vec::new()的默认容量就是 0。
use std::path::PathBuf;fn main() {let mut buf1 = PathBuf::new();// 动态扩容buf1.push("/a/b/c");println!("{:?}", buf1);
}
"/a/b/c"
2.2.2 PathBuf::from创建
从字符串初始化
use std::path::{Path, PathBuf};fn main() {let buf:PathBuf = PathBuf::from("/home/user");
}
2.2.3 PathBuf::with_capacity创建
预分配
n字节容量的空PathBuf已知路径大致长度,追求性能优化
use std::path::PathBuf;fn main() {// 预分配 20 字节容量的 PathBuf(适合已知路径大致长度的场景)let mut path_buf = PathBuf::with_capacity(20);path_buf.push("/a/b/c/new_dir"); // 缓冲区足够,无需扩容println!("{:?}", path_buf);
}
"/a/b/c/new_dir"
2.3 转换
2.3.1 Path
Path转换PathBuf
p.into()和p.to_path_buf()都能将 &Path 转换为 PathBuf
- 实现机制
- to_path_buf():基于
&Path的数据创建一个新的PathBuf - into():
Into trait实现。PathBuf为&Path实现了Into<PathBuf>,而这个实现的内部本质上就是调用to_path_buf()
- to_path_buf():基于
use std::path::{Path, PathBuf};fn main() {let p = Path::new("/home/user");// into()转换// let buf: PathBuf = p.into();let buf: PathBuf = p.to_path_buf();
}
2.3.2 PathBuf
PathBuf转换Path
pb.as_path()进行转化
use std::path::{Path, PathBuf};fn main() {let buf = PathBuf::from("/home/user");// as_path转换let p: &Path = buf.as_path();
}
3.路径拼接
使用
join()进行拼接,会创建新PathBuf
3.1Path拼接路径
3.1.1 join
返回新
PathBuf,适合临时使用基础路径拼接场景
use std::path::Path;fn main() {let path = Path::new("/home/user");let new_path:PathBuf = path.join("subdir").join("file.txt");println!("path: {:?}", path);println!("new_path: {:?}", new_path);
}
path: "/home/user"
new_path: "/home/user/subdir/file.txt"
3.2 PathBuf拼接路径
3.2.1 join
和
Path.join一样,返回新的PathBuf,对原有的Path/PathBuf不进行修改
use std::path::PathBuf;fn main() {let path = Path::new("/home/user");let new_path:PathBuf = path.join("subdir").join("file.txt");println!("path: {:?}", path);println!("new_path: {:?}", new_path);
}
path: "/home/user"
new_path: "/home/user/subdir/file.txt"
3.2.2 push
直接修改原
PathBuf,在自身末尾拼接路径组件
use std::path::PathBuf;fn main() {let mut buf = PathBuf::from("/home/user");buf.push("subdir");buf.push("file.txt");println!("buf: {:?}", buf);
}
buf: "/home/user/subdir/file.txt"
4. 提取路径组件
4.1 文件名与扩展名
Path和PathBuf操作一样
4.1.1 提取
file_name、extension、file_stem
use std::path::Path;fn main() {let path = Path::new("/home/user/docs/note.txt");// 获取文件名(含扩展名)println!("文件名: {:?}", path.file_name()); // Some("note.txt")// 获取扩展名(不含".")println!("扩展名: {:?}", path.extension()); // Some("txt")// 获取文件名(不含扩展名)println!("纯文件名: {:?}", path.file_stem()); // Some("note")
}
文件名: Some("note.txt")
扩展名: Some("txt")
纯文件名: Some("note")
4.1.2 替换扩展名
with_extension会返回新的PathBuf
use std::path::Path;fn main() {let path = Path::new("/home/user/docs/note.txt");let new_path = path.with_extension("mp4");println!("替换扩展名:{:?}", new_path);
}
替换扩展名:"/home/user/docs/note.mp4"
4.1.3 替换文件名
会返回新的
PathBuf
use std::path::{Path};fn main() {let path = Path::new("/home/user/docs/aaa.txt");let new_path = path.with_file_name("summary.pdf");println!("替换扩展名:{:?}", new_path); // 输出 "/home/user/docs/report.txt"
}
替换扩展名:"/home/user/docs/summary.pdf"
4.2 提取路径
4.2.1 提取父目录
parent()(其实等价于.ancestors().nth(1))
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/home/user/docs/aaa.txt");println!("文件名: {:?}", path.parent());let path_buf = PathBuf::from("/home/user/docs/bbb.txt");println!("文件名: {:?}", path_buf.parent());
}
文件名: Some("/home/user/docs")
文件名: Some("/home/user/docs")
4.2.2 获取多层父目录
Path::parent()每调用一次,只会返回 上一级目录,
如果你想向上多级,就需要多次调用,或者更优雅地用ancestors()
4.2.2.1 ancestors
ancestors()会自动生成一个迭代器Iterator<Item = &Path>
use std::path::Path;fn main() {let path = Path::new("/usr/local/bin/rustc");for ancestor in path.ancestors() {println!("{:?}", ancestor);}
}
"/usr/local/bin/rustc"
"/usr/local/bin"
"/usr/local"
"/usr"
"/"
4.2.2.2 ancestors + nth
use std::path::Path;fn main() {let path = Path::new("/usr/local/bin/rustc");let parent_1 = path.ancestors().nth(1).unwrap(); // 上一级let parent_2 = path.ancestors().nth(2).unwrap(); // 上两级let parent_3 = path.ancestors().nth(3).unwrap(); // 上三级println!("上一级: {:?}", parent_1);println!("上两级: {:?}", parent_2);println!("上三级: {:?}", parent_3);
}
上一级: "/usr/local/bin"
上两级: "/usr/local"
上三级: "/usr"
4.2.3 components
返回一个迭代器
Iterator<Item = Component>
Component::RootDir:表示根目录(如 Unix 下的 /、Windows 下的 \)。
Component::Normal(s):表示普通的目录名或文件名(如 home、note.txt)。Windows 特有
Component::Prefix(...):表示盘符前缀(如 C:)。
- 与
ancestors区别是path.ancestors(): 从尾到头(子 → 父)path.components():从头到尾(根 → 子)
4.2.3.1 遍历所有组件
use std::path::Path;fn main() {let path = Path::new("/usr/local/bin/rustc");for comp in path.components() {println!("{:?}", comp);}
}
RootDir
Normal("usr")
Normal("local")
Normal("bin")
Normal("rustc")
4.2.3.2 配合match匹配
use std::path::{Path, Component};fn main() {// Unix 风格路径示例let unix_path = Path::new("/home/user/docs/note.txt");println!("Unix 路径组件:");for comp in unix_path.components() {match comp {Component::RootDir => println!(" 根目录: /"),Component::Normal(name) => println!(" 普通组件: {}", name.to_string_lossy()),_ => println!(" 其他组件: {:?}", comp),}}
}
4.2.3.3 take(n)
只取前
n个元素,之后迭代器就结束
path.components().take(n).collect();可以截取前n个组件
use std::path::{Component, Path, PathBuf};fn main() {let path = std::path::Path::new("/home/user/docs/file.txt");for comp in path.components().take(3) {println!("{:?}", comp);}
}
RootDir
Normal("home")
Normal("user")
4.2.3.4 skip(n)
跳过前
n个元素,之后迭代器就结束
use std::path::{Component, Path, PathBuf};fn main() {let path = std::path::Path::new("/home/user/docs/file.txt");for comp in path.components().skip(3) {println!("{:?}", comp);}
}
Normal("docs")
Normal("file.txt")
5.路径属性判断
Path和PathBuf这两个类型都可以使用这些方法
5.1 检测路径开头starts_with
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/home/user/photo.jpg");let head_path = Path::new("/home/user");let res = path.starts_with(head_path);println!("{}", res);
}
true
5.2 路径/文件是否存在exists
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/a/b/c.txt");println!("exists: {}", path.exists());
}
exists: false
5.3 判断文件is_file
文件不存在、目录、无权限访问都会返回
false
use std::path::{Path, PathBuf};fn main() {// 文件不存在let path = Path::new("/a/b/c.txt");println!("is_file: {}", path.is_file());let path = Path::new("/Users/my_space/Code/rust_code/rc_test/data.txt");println!("is_file: {}", path.is_file());
}
is_file: false
is_file: true
5.4 判断目录is_dir
如果
路径不存在和无权限访问的也返回false
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/Users/my_space/Code/rust_code/rc_test/data.txt");println!("is_dir: {}", path.is_dir());let path = Path::new("/Users/my_space/Code/rust_code/rc_test");println!("is_dir: {}", path.is_dir());
}
is_dir: false
is_dir: true
5.5 获取元数据metadata
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/Users/my_space/Code/rust_code/rc_test/data.txt");println!("metadata: {:?}", path.metadata());
}
metadata: Ok(Metadata { file_type: FileType { is_file: true, is_dir: false, is_symlink: false, .. }, permissions: Permissions(FilePermissions { mode: 0o100644 (-rw-r--r--) }), len: 12, modified: SystemTime { tv_sec: 1762789499, tv_nsec: 901106416 }, accessed: SystemTime { tv_sec: 1762789501, tv_nsec: 567357216 }, created: SystemTime { tv_sec: 1762789499, tv_nsec: 901025875 }, .. })
5.5.1 获取元数据
meta.accessed()获取的是SystemTime如果需要转化为年月日时分秒,使用第三方
chrono库
use std::fs;
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/Users/my_space/Code/rust_code/rc_test/data.txt");match fs::metadata(path) {Ok(meta) => {println!("是否文件: {}", meta.is_file());println!("是否目录: {}", meta.is_dir());println!("文件大小: {} 字节", meta.len());println!("最后修改时间: {:?}", meta.modified());println!("最后访问时间: {:?}", meta.accessed());println!("文件创建时间: {:?}", meta.created());println!("文件类型: {:?}", meta.file_type());}Err(e) => eprintln!("读取元数据失败: {}", e),}
}
是否文件: true
是否目录: false
文件大小: 12 字节
最后修改时间: Ok(SystemTime { tv_sec: 1762789499, tv_nsec: 901106416 })
最后访问时间: Ok(SystemTime { tv_sec: 1762789501, tv_nsec: 567357216 })
文件创建时间: Ok(SystemTime { tv_sec: 1762789499, tv_nsec: 901025875 })
文件类型: FileType { is_file: true, is_dir: false, is_symlink: false, .. }
5.6 判断是否是软连接is_symlink
创建一个软链接
ln -s data.txt data_ln_s.txt
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/Users/my_space/Code/rust_code/rc_test/data.txt");println!("data.txt 是符号链接吗? {}", path.is_symlink());let ln_s_path = Path::new("/Users/my_space/Code/rust_code/rc_test/data_ln_s.txt");println!("ata_ln_s.txt 是符号链接吗? {}", ln_s_path.is_symlink());
}
data.txt 是符号链接吗? false
ata_ln_s.txt 是符号链接吗? true
5.7 判断绝对路径is_absolute
use std::path::PathBuf;fn main() {let path_buf = PathBuf::from("/a/b/c.txt");let is_absolute = path_buf.is_absolute();println!("is_absolute:{}", is_absolute);
}
5.8 判断绝对路径is_relative
use std::path::PathBuf;fn main() {let path_buf = PathBuf::from("../a/b/c.txt");let is_relative = path_buf.is_relative();println!("is_relative:{}", is_relative);
}
is_relative:true
5.9 判断有根路径has_root
use std::path::PathBuf;fn main() {let path_buf1 = PathBuf::from("../a/b/c.txt");let has_root1 = path_buf1.has_root();println!("has_root:{}", has_root1);let path_buf2 = PathBuf::from("/a/b/c.txt");let has_root2 = path_buf2.has_root();println!("has_root:{}", has_root2);
}
has_root:false
has_root:true
6.路径/文件替换
6.1文件替换
6.1.1 Path替换
返回一个新的
PathBuf
use std::path::Path;fn main() {let mut path = Path::new("/home/user/note.txt");// 替换文件名(含扩展名)let p1 = path.with_file_name("new_note.md");println!("替换文件名后: {:?}", p1);// 替换扩展名(仅修改扩展名部分)let p2 = path.with_extension("txt");println!("替换扩展名后: {:?}", p2);
}
替换文件名后: "/home/user/new_note.md"
替换扩展名后: "/home/user/note.txt"
6.1.2 PathBuf替换
原地修改,不返回新的PathBuf
Path没有set_file_name和set_extension方法如果需要替换
Path,先通过to_path_buf()转换成PathBuf
use std::path::PathBuf;fn main() {let mut path = PathBuf::from("/home/user/note.txt");// 替换文件名(含扩展名)path.set_file_name("new_note.md");println!("替换文件名后: {:?}", path);// 替换扩展名(仅修改扩展名部分)path.set_extension("txt");println!("替换扩展名后: {:?}", path);
}
替换文件名后: "/home/user/new_note.md"
替换扩展名后: "/home/user/new_note.txt"
6.2 路径替换
6.2.1 替换路径前缀strip_prefix
strip_prefix(old_prefix):检查原始路径是否以 old_prefix 开头,若符合则返回
移除前缀后的子路径。若不匹配则返回 None(需处理错误)
6.2.1.1 strip_prefix用法
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/home/user/photo.jpg");let old_p1 = Path::new("/home/user");let old_p2 = Path::new("/home/abc");let s1 = path.strip_prefix(old_p1);println!("{:?}", s1);let s2 = path.strip_prefix(old_p2);println!("{:?}", s2);
}
Ok("photo.jpg")
Err(StripPrefixError(()))
6.2.1.2 结合if判断处理
use std::path::{Path, PathBuf};fn main() {let path = Path::new("/home/user/photo.jpg");let old_p = Path::new("/home/user");let new_p = Path::new("/home/user/abc");if let Ok(new_path) = path.strip_prefix(old_p) {println!("替换后: {:?}", new_p.join(new_path));} else {println!("替换失败");}
}
替换后: "/home/user/abc/photo.jpg"
6.2.2 替换中间路径
配合
Component,这里没有适配window的环境
Component+map
use std::path::{Path, PathBuf, Component};fn main() {let path = Path::new("/a/b/c/old_dir/d/file.txt");let old = "old_dir";let new = "new_dir";let new_path:PathBuf = path.components().map(|comp| if let Component::Normal(os_str) = comp {// os_str.to_str()将&OsStr转为Option<&str>// Some(old)将&str转换成Option<&str>if os_str.to_str() == Some(old) {Component::Normal(new.as_ref())} else {comp}} else {comp}).collect();println!("{:?}", new_path)
}
"/a/b/c/new_dir/d/file.txt"
Component+for循环
use std::path::{Component, Path, PathBuf};fn main() {let path = Path::new("/a/b/c/old_dir/d/file.txt");let old = "old_dir";let new = "new_dir";// 创建一个新的pathbuf对象let mut new_path = PathBuf::with_capacity(path.as_os_str().len());for comp in path.components() {match comp {Component::RootDir => {new_path.push(comp);}Component::Normal(name) => {// 安全处理:先将 &OsStr 转为 &str(非UTF-8路径直接保留原组件)if let Some(name_str) = name.to_str() {if name_str == old {// 匹配旧组件,push 新组件new_path.push(new);} else {// 不匹配,push 原组件new_path.push(name);}} else {// 非 UTF-8 组件,直接保留new_path.push(name);}}_ => {}}}println!("{:?}", new_path)
}
"/a/b/c/new_dir/d/file.txt"