4. SPIFFS 目录操作:创建目录、遍历目录、删除目录与文件
文件系统嘛,光会读写文件肯定不够。你想想看,一个正经的嵌入式项目,尤其是带网页服务器的,文件一多就得分类管理。CSS 放一个文件夹,JS 放一个,图片再放一个——这时候目录操作就派上用场了。
我个人习惯,在项目初期就把 SPIFFS 的目录结构规划好。别等到文件堆了一堆再回头整理,那可就麻烦了。这一节,咱们就聊聊怎么在 SPIFFS 里创建目录、遍历目录,以及怎么安全地删除文件和目录。
4.1 创建目录:mkdir() 的正确用法
SPIFFS 的 mkdir() 函数,说白了就是告诉文件系统:「嘿,我要在这个路径下放文件了,你帮我记一下。」但它跟 PC 上的 mkdir 不太一样——它不会真的创建「文件夹」实体,只是注册一个路径前缀。
#include <SPIFFS.h>
void setup() {
Serial.begin(115200);
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS 挂载失败");
return;
}
// 创建目录(实际上是注册路径)
if (SPIFFS.mkdir("/www")) {
Serial.println("/www 目录创建成功");
} else {
Serial.println("/www 目录创建失败");
}
// 创建多级路径
if (SPIFFS.mkdir("/www/css")) {
Serial.println("/www/css 创建成功");
}
if (SPIFFS.mkdir("/www/js")) {
Serial.println("/www/js 创建成功");
}
}
我在项目中遇到过一个问题:明明调用了 mkdir(),返回也是 true,但用 openDir() 遍历时却看不到这个目录。后来才发现,空目录在 SPIFFS 里是不存在的。只有当你往这个路径下写了一个文件,目录才会真正「出现」。
4.2 遍历目录:openDir() 与文件枚举
遍历目录,说白了就是「翻翻这个文件夹里都有啥」。SPIFFS 提供了 openDir() 函数,返回一个目录迭代器,你可以用它来逐个读取文件和子目录。
void listDir(const char *dirname) {
Serial.printf("列出目录: %s\n", dirname);
File root = SPIFFS.open(dirname);
if (!root) {
Serial.println("打开目录失败");
return;
}
if (!root.isDirectory()) {
Serial.println("这不是一个目录");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.printf(" [DIR] %s\n", file.name());
} else {
Serial.printf(" [FILE] %s (大小: %d 字节)\n",
file.name(), file.size());
}
file = root.openNextFile();
}
root.close();
}
嗯,这里要注意:openNextFile() 返回的 File 对象,用完一定要 close()。我记得有一次调试,发现内存占用越来越高,排查了半天才发现是遍历时没关文件句柄。
4.3 删除文件与目录:remove() 与 rmdir()
删除操作看起来简单,但坑不少。先看基本用法:
// 删除单个文件
if (SPIFFS.remove("/www/style.css")) {
Serial.println("文件删除成功");
} else {
Serial.println("文件删除失败");
}
// 删除目录(目录必须为空)
if (SPIFFS.rmdir("/www/css")) {
Serial.println("目录删除成功");
} else {
Serial.println("目录删除失败,可能不为空");
}
rmdir() 有个硬性要求:目录必须为空。如果目录里还有文件,它会返回 false。那怎么删除非空目录呢?得自己写递归:
bool deleteRecursive(const char *path) {
File file = SPIFFS.open(path);
if (!file) return false;
// 如果是目录,先清空里面的内容
if (file.isDirectory()) {
File child = file.openNextFile();
while (child) {
char childPath[64];
snprintf(childPath, sizeof(childPath), "%s/%s",
path, child.name());
if (child.isDirectory()) {
deleteRecursive(childPath); // 递归删除子目录
} else {
SPIFFS.remove(childPath); // 删除文件
}
child = file.openNextFile();
}
file.close();
return SPIFFS.rmdir(path); // 目录清空后删除
} else {
file.close();
return SPIFFS.remove(path); // 直接删除文件
}
}
remove() 和 rmdir() 操作是不可逆的。删除前最好做个确认,尤其是 OTA 升级时,别把正在运行的网页文件给删了。
4.4 目录操作的核心流程
下面这张图,是我在实际项目中总结的目录操作流程。你照着这个顺序来,基本不会出问题。
4.5 目录操作函数速查表
为了方便你查阅,我把常用的目录操作函数整理成了表格。建议你收藏一下,写代码时直接翻出来看。
| 函数 | 功能 | 返回值 | 注意事项 |
|---|---|---|---|
SPIFFS.mkdir(path) |
创建目录 | bool | 空目录不可见,需放占位文件 |
SPIFFS.openDir(path) |
打开目录迭代器 | Dir 对象 | 路径不存在时返回空迭代器 |
dir.next() |
移动到下一个文件 | bool | 返回 false 表示遍历结束 |
dir.fileName() |
获取当前文件名 | String | 包含完整路径 |
dir.fileSize() |
获取当前文件大小 | size_t | 目录大小为 0 |
SPIFFS.remove(path) |
删除文件 | bool | 只能删文件,不能删目录 |
SPIFFS.rmdir(path) |
删除空目录 | bool | 目录不为空时返回 false |
SPIFFS.exists(path) |
检查路径是否存在 | bool | 文件和目录都适用 |
4.6 实战:网页资源目录管理
说了这么多理论,咱们来点实际的。假设你要做一个智能家居控制面板,网页资源需要分类存放。下面这段代码,展示了完整的目录初始化流程:
bool initWebDirectories() {
// 定义需要的目录结构
const char* dirs[] = {
"/www",
"/www/css",
"/www/js",
"/www/img",
"/www/fonts"
};
for (int i = 0; i < 5; i++) {
if (!SPIFFS.exists(dirs[i])) {
if (!SPIFFS.mkdir(dirs[i])) {
Serial.printf("创建目录失败: %s\n", dirs[i]);
return false;
}
// 创建占位文件,确保目录可见
String placeholder = String(dirs[i]) + "/.keep";
File f = SPIFFS.open(placeholder, "w");
if (f) f.close();
}
}
// 验证目录结构
Serial.println("目录结构验证:");
listDir("/www");
return true;
}
void setup() {
Serial.begin(115200);
SPIFFS.begin(true);
if (initWebDirectories()) {
Serial.println("网页目录初始化成功");
} else {
Serial.println("网页目录初始化失败");
}
}
exists() 检查目录是否存在。如果不存在就创建,存在就跳过。这样即使设备意外断电重启,目录结构也不会乱。
4.7 避坑指南:目录操作的常见问题
做嵌入式开发这么多年,我在目录操作上踩过的坑,说出来都是泪。下面这几个问题,你大概率也会遇到:
- 路径分隔符问题: SPIFFS 统一用 "/",别用 Windows 的 "\"。我见过有人从 PC 复制路径过来,结果死活打不开文件。
- 文件名大小写: SPIFFS 是大小写敏感的。"Index.html" 和 "index.html" 是两个不同的文件。建议统一用小写。
- 路径长度限制: 单个路径不要超过 32 个字符。太长的话,
open()会返回空指针。 - 并发访问: 不要在中断服务程序里操作 SPIFFS。文件系统不是线程安全的,同时操作会导致数据损坏。
- 删除后的空间释放: 删除文件后,SPIFFS 不会立即回收空间。需要调用
SPIFFS.info()查看实际使用情况。