如何使用C语言scandir按创建顺序检索文件及相关疑问
嘿,这个问题我之前做项目时也碰到过!确实,readdir()的遍历顺序完全由底层文件系统决定,完全不保证按创建时间排序,用scandir()配合自定义比较函数是个非常靠谱的思路。咱们来逐个解决你的疑问:
疑问1:能不能用d_ino字段替代stat()高效获取创建时间?
很遗憾,这行不通。d_ino是文件的inode编号,它只是文件系统用来唯一标识文件的ID,和文件的创建时间没有稳定的对应关系。不同文件系统的inode分配规则差异很大:
- 有些传统文件系统(比如ext2/3)会按顺序分配inode,新文件的inode号会更大,但它也会复用已删除文件释放的inode;
- 像Btrfs这类现代文件系统,甚至会随机分配inode号。
所以完全不能通过inode号的大小来判断文件的创建顺序,你还是得通过stat()(或者lstat(),如果需要区分符号链接本身和目标文件的话)来获取真实的创建时间戳。
不过可以给你个性能优化小技巧:如果目录里文件数量很多,在比较函数里每次调用stat()会重复发起IO,浪费资源。你可以先把所有dirent条目收集起来,一次性给每个文件调用一次stat()缓存时间戳,再进行排序——这样能避免重复IO,提升效率。
疑问2:关于scandir()比较函数的签名
scandir()要求的比较函数签名是固定的:
int (*compar)(const struct dirent **, const struct dirent **)
这个函数接收两个指向struct dirent指针的指针,返回值规则是:
- 返回负数:第一个条目应该排在第二个前面;
- 返回零:两个条目顺序无关;
- 返回正数:第二个条目应该排在第一个前面。
下面给你一个按创建时间排序的示例实现(注意不同系统的创建时间字段有差异:Linux下如果要拿真正的创建时间需要用statx(),示例里先用st_ctime状态改变时间代替;BSD/macOS用st_birthtime,Windows则是ftCreationTime):
#include <stdio.h> #include <dirent.h> #include <sys/stat.h> #include <string.h> #include <stdlib.h> #include <time.h> // 辅助函数:获取文件创建时间(这里以Linux为例,实际根据系统调整) time_t get_file_creation_time(const char *dir_path, const char *filename) { char full_path[PATH_MAX]; if (snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, filename) >= sizeof(full_path)) { fprintf(stderr, "Path too long\n"); return -1; } struct stat st; if (stat(full_path, &st) == -1) { perror("Failed to get file stat"); return -1; } // Linux下st_ctime是状态改变时间,真正的创建时间请用statx() // BSD/macOS替换为st_birthtime return st.st_ctime; } // scandir的比较函数:按创建时间升序排列 // 注意:这里为了简化用了全局变量存目录路径,实际项目建议用更优雅的封装(比如先收集条目再排序) static char g_target_dir[PATH_MAX]; int compare_by_creation_time(const struct dirent **a, const struct dirent **b) { time_t time_a = get_file_creation_time(g_target_dir, (*a)->d_name); time_t time_b = get_file_creation_time(g_target_dir, (*b)->d_name); if (time_a == -1 || time_b == -1) { // 处理stat失败的情况,这里默认把错误的条目放后面 return 1; } if (time_a < time_b) return -1; if (time_a > time_b) return 1; return 0; } // 使用示例 int main(int argc, char *argv[]) { const char *target_dir = "."; if (argc > 1) { target_dir = argv[1]; strncpy(g_target_dir, target_dir, sizeof(g_target_dir)-1); g_target_dir[sizeof(g_target_dir)-1] = '\0'; } else { strcpy(g_target_dir, "."); } struct dirent **namelist; int n = scandir(target_dir, &namelist, NULL, compare_by_creation_time); if (n == -1) { perror("scandir failed"); return EXIT_FAILURE; } printf("Files sorted by creation time:\n"); while (n--) { printf(" %s\n", namelist[n]->d_name); free(namelist[n]); } free(namelist); return EXIT_SUCCESS; }
更优雅高效的优化方案
上面的示例用了全局变量传递目录路径,其实不太优雅。更推荐的做法是:
- 先用
scandir()获取所有目录条目; - 创建一个结构体数组,每个元素保存
dirent指针和对应的创建时间戳; - 用
qsort()对这个结构体数组按时间戳排序; - 遍历处理排序后的数组;
- 最后释放所有资源。
这种方式每个文件只需要调用一次stat(),避免了比较函数里的重复IO,性能更好,代码结构也更清晰。
内容的提问来源于stack exchange,提问作者S. Forman

