遍历目录的主要思想
由于目录就是一颗树,所以遍历目录就转换为遍历一棵树。谈到树的遍历就再熟悉不过了,有树的前序、层次和后序遍历,我使用的是前序遍历,后序遍历和前序遍历本质上一样,而层次遍历要比前两个麻烦些,我两个都实现了,现在贴出来分享下。
前序遍历
前序遍历和树的遍历一样,我先显示当前目录的信息,然后遍历目录中的目录项,如果目录项是一个目录则先递归这个子目录,否则如果是目录项是非目录的话就返回。
static void DoTraverDir(MyFunc myFunc) { struct stat statBuf; DIR *pDir; struct dirent *pDirent; int pathLen, direntLen; // printf("path: %s\n", fullPath); //获取当前目录信息,用lstat防止嵌套调用 if (lstat(fullPath, &statBuf) == 0) myFunc(fullPath, &statBuf, TD_F); else { myFunc(fullPath, &statBuf, TD_NS); //无法获取信息 return; } if (!S_ISDIR(statBuf.st_mode)) //当前路径为文件 return; //处理目录 pathLen = strlen(fullPath); if (pathLen >= MAX_PATH - 2) //目录长度限制,不再处理更深层的目录 return; if ((pDir = opendir(fullPath)) == NULL) { myFunc(fullPath, &statBuf, TD_DNR); //无法读取目录 return; } if (fullPath[pathLen - 1] != '/') fullPath[pathLen++] = '/'; //遍历目录中的所有目录项 while ((pDirent = readdir(pDir)) != NULL) { //忽略.和..目录项 if (strcmp(pDirent->d_name, ".") == 0 || strcmp(pDirent->d_name, "..") == 0) continue; direntLen = strlen(pDirent->d_name); if (pathLen + direntLen > MAX_PATH) //路径超过了最大长度 return; strcpy(fullPath + pathLen, pDirent->d_name); fullPath[pathLen + direntLen] = 0; DoTraverDir(myFunc); //递归处理下一层 } fullPath[pathLen - 1] = 0; if (closedir(pDir) == -1) printf("close dir error : %s\n", fullPath); }
代码中的fullPath是一个全局变量,用来存放当前遍历文件的路径,路径的最大长度为4096,超过了4096函数自己返回,不进行任何的处理,定义如下:
1 #define MAX_PATH 4096 2 static char fullPath[MAX_PATH + 1];
还有一个地方值得注意,那就是我上面红色标识的代码,遍历完目录后必须将当前目录关闭掉,否则程序占有的打开目录资源会超过系统的限制,当遍历到了一定的数量后,后面的遍历都会失败,我就是开始没有关闭目录,所以后面出现莫名其妙的错误。
层次遍历
层次遍历要比前序遍历复杂点,因为是要先处理好了当前目录中的所有目录项后再处理下一层的目录。所有在遍历当前目录的目录项时必须保存下层目录的路径信息,以方便处理下层目录。不过和前面代码的实现也差不多,就是多了一个保存路径的容器罢了,代码如下:
static void DoTraverDir(MyFunc myFunc) { struct stat statBuf; DIR *pDir; struct dirent *pDirent; int pathLen, direntLen; std::vector<std::string> vpDirent; std::vector<std::string>::iterator vpDirentIterator; std::string str; // printf("path: %s\n", fullPath); //获取当前目录信息,用lstat防止嵌套调用 if (lstat(fullPath, &statBuf) == 0) myFunc(fullPath, &statBuf, TD_F); else { myFunc(fullPath, &statBuf, TD_NS); //无法获取信息 return; } //处理目录 pathLen = strlen(fullPath); if (pathLen >= MAX_PATH - 2) //目录长度限制,不再处理更深层的目录 return; if ((pDir = opendir(fullPath)) == NULL) { myFunc(fullPath, &statBuf, TD_DNR); //无法读取目录 return; } if (!S_ISDIR(statBuf.st_mode)) //当前路径为文件 return; if (fullPath[pathLen - 1] != '/') fullPath[pathLen++] = '/'; //遍历目录中的所有目录项 while ((pDirent = readdir(pDir)) != NULL) { //忽略.和..目录项 if (strcmp(pDirent->d_name, ".") == 0 || strcmp(pDirent->d_name, "..") == 0) continue; direntLen = strlen(pDirent->d_name); if (pathLen + direntLen > MAX_PATH) //路径超过了最大长度 return; strcpy(fullPath + pathLen, pDirent->d_name); fullPath[pathLen + direntLen] = 0; if (lstat(fullPath, &statBuf) != 0) { myFunc(fullPath, &statBuf, TD_NS); //无法获取信息 continue; } if (S_ISDIR(statBuf.st_mode)) //当前路径为目录 vpDirent.push_back(pDirent->d_name); else myFunc(fullPath, &statBuf, TD_F); } if (closedir(pDir) == -1) { fullPath[pathLen - 1] = 0; printf("close dir error : %s\n", fullPath); } for (vpDirentIterator = vpDirent.begin(); vpDirentIterator != vpDirent.end(); ++vpDirentIterator) { str = *vpDirentIterator; direntLen = str.length(); strcpy(fullPath + pathLen, str.c_str()); fullPath[pathLen + direntLen] = 0; DoTraverDir(myFunc); } }
回调函数
上面程序中的回调函数MyFunc是用来显示每个文件的信息和对所遍历信息的统计,下面是该函数的具体实现:
void ShowInfo(const char *pathName, const struct stat *statBuf, int type) { switch (type) { case TD_F: if (S_ISREG(statBuf->st_mode)) //普通文件 nReg++; else if (S_ISDIR(statBuf->st_mode)) //目录 nDir++; else if (S_ISCHR(statBuf->st_mode)) //字符文件 nChr++; else if (S_ISBLK(statBuf->st_mode)) //块文件 nBlk++; else if (S_ISFIFO(statBuf->st_mode)) //管道文件 nFifo++; else if (S_ISLNK(statBuf->st_mode)) //链接文件 nLink++; else if (S_ISSOCK(statBuf->st_mode)) //套接字文件 nSock++; else //未知文件类型 { nKno++; printf("unknow file type : %s\n", pathName); } break; case TD_NS: nNs++; printf("can't state file : %s\n", pathName); break; case TD_DNR: nDnr++; printf("can't open dir : %s\n", pathName); break; default: printf("unkonw type\n"); } }
稳定、高效、健壮