美文网首页
一段二分查找代码

一段二分查找代码

作者: 明翼 | 来源:发表于2020-12-26 17:14 被阅读0次

    一 背景

    有个需求,要从大量的文件中过滤需要的文件,文件名那是以时间命令(UTC时间)的,要过滤的文件范围也是符合一定时间段的文件。

    二 初始版本

    代码比较简单和直接,通过直接读取目录下的文件,获取每个文件的文件名信息,判断每个文件名是否在要求的时间范围内,是的话就放入到文件名列表中,最后返回。
    这段代码,从感觉上,性能一般,如果有几百万个文件,甚至更多个文件,则每个文件名都要判断一次耗时是惊人的,那能不能根据文件名直接做二分查找的方式来过滤文件名那。

    int thisFileNeedSearch(char* fileName, int startTime, int endTime)
    {
        if (fileName == NULL) {
            return -1;
        }
        char* flag = ".";
        char* ptmp = strdup(fileName);
        strtok(ptmp, flag);
        strtok(NULL, flag);
        char* ret = strtok(NULL, flag);
        if (ret != NULL) {
            int iret = atoi(ret);
            if (iret >= startTime && iret <= endTime) {
                free(ptmp);
                return 1;
            } else {
                free(ptmp);
                return 0;
            }
        } else {
            cl_error("file is error:%s", fileName);
            free(ptmp);
            return -1;
        }
    }
    
    fileLists* getNeedSearchFilesOld(char* dir_name, int startTime, int endTime)
    {
        DIR* dir = opendir(dir_name);
        struct dirent* p = NULL;
        struct stat statbuf;
        fileLists* lst = NULL;
        cl_infos("Scan dir is:%s", dir_name);
        if (lstat(dir_name, &statbuf)) {
            cl_error("Stat error:%s", dir_name);
            return NULL;
        }
        if (!S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) {
            cl_error("Dir :%s is not dir mode is:%d!", dir_name, statbuf.st_mode);
            return NULL;
        }
        while ((p = readdir(dir)) != NULL) {
            // 过滤掉. 和..
            if (p->d_name[0] == '.') {
                continue;
            }
            if (thisFileNeedSearch(p->d_name, startTime, endTime) == 1) {
                if (lst == NULL) {
                    lst = (fileLists*)malloc(sizeof(fileLists));
                    assert(lst != NULL);
                    lst->fileName = strdup(p->d_name);
                    lst->next = NULL;
                    lst->size = 1;
                    lst->tail = lst;
                } else {
                    fileLists* plst = (fileLists*)malloc(sizeof(fileLists));
                    assert(plst != NULL);
                    plst->fileName = strdup(p->d_name);
                    plst->next = NULL;
                    plst->size = 1;
                    lst->tail->next = plst;
                    lst->tail = plst;
                    lst->size += 1;
                }
            } else {
                cl_debug("file:%s not in start:%d end:%d", p->d_name, startTime, endTime);
            }
        }
        closedir(dir);
        return lst;
    }
    

    三 二分查找版本

    要查找文件的范围是个区间[starttime,endtime],一般endtime和starttime的差值并不算多大,那么我们可以先对文件名做个排序,然后根据二分查找的算法,查找到开始遍历starttime的文件名,逐步对后遍历即可。

    直接读目录的方式,在排序挺麻烦,幸好linux有另外一个遍历文件的接口如下:

    #include <dirent.h>
    
    int scandir( const char *dir,  struct dirent **namelist,  int (*filter) (const void *b),  int ( * compare )( const struct dirent **, const struct dirent ** ) );
    int alphasort(const void *a, const void *b);
    int versionsort(const void *a, const void *b);
    
    

    提供两个接口,一个是filter接口,即过滤需要的文件名,二是比较接口,即比较排序的时候使用,我们可以直接使用alphasort进行文件名的比较。如下:

    // 保留文件名中包含file的文件
    int file_filter(const struct dirent* dir)
    {
        const char* name = dir->d_name;
        int len = strlen(name);
        if (MIN_FILE_NAME_LEN > len) {
            return 0;
        } else {
            char* pos = strstr(dir->d_name, "file");
            if (pos == NULL) {
                return 0;
            } else {
                return 1;
            }
        }
    }
    
    fileLists* getNeedSearchFiles(char* dir_name, int startTime, int endTime)
    {
        struct dirent** entry_list = NULL;
        int count = scandir(dir_name, &entry_list, pcap_filter, alphasort);
        cl_infos("files number:%d", count);
        if (count < 1) {
            cl_infos("No find file is dir:%d", dir_name);
            return NULL;
        }
        int fileStart = fileNameToInt(entry_list[0]->d_name);
        cl_infos("start filename:%s", entry_list[0]->d_name);
        // 最小值大于要查询的最大值 直接返回
        if (fileStart > endTime) {
            freeEntryList(entry_list, count);
            free(entry_list);
            cl_infos("file start time:%d is bigger than endtime:%d", fileStart, endTime);
            return NULL;
        }
        if (count > 1) {
            // 最大值比最小值都小 直接返回
            int fileEnd = fileNameToInt(entry_list[count - 1]->d_name);
            cl_infos("end filename:%s", entry_list[count - 1]->d_name);
            if (fileEnd < startTime) {
                freeEntryList(entry_list, count);
                free(entry_list);
                cl_infos("file end time:%d is small than start:%d", fileEnd, startTime);
                return NULL;
            }
        }
        int low = 0;
        int high = count - 1;
        fileLists* lst = NULL;
        int startIndex = 0;
       // 二分查找文件范围
        while (low <= high) {
            int mid = low + (high - low) / 2;
            struct dirent* entry = entry_list[mid];
            int filetime = fileNameToInt(entry->d_name);
            // 说明要取得范围在左边
            if (filetime > startTime) {
                startIndex = mid;
                high = mid - 1;
                cl_infos("set startIndex:%d", startIndex);
            }
            // 说明文件范围在右边
            else if (filetime < startTime) {
                low = mid + 1;
            } else {
                // 找到最小值了
                startIndex = mid;
                break;
            }
        }
        for (int j = startIndex; j < count; j++) {
            int filetime = fileNameToInt(entry_list[j]->d_name);
            if (filetime >= startTime && filetime <= endTime) {
                addToFileList(&lst, entry_list[j]->d_name);
            }
        }
        freeEntryList(entry_list, count);
        free(entry_list);
        return lst;
    }
    

    说明下 : int mid = low + (high - low) / 2; 只所以有这种写法是为了防止low+high越界。

    代码不算啥,给大家一个二分查找一段范围的文件的一点思路吧。

    四 测试

    写完了,免不了测试下,结果有点令人惊讶,如果文件数目少于9万个,其实老版本的性能反而更好,差别不是特别大,原因是scandir的过滤和排序拉慢了速度,而且核心逻辑只是转int和比较,在较小的数量级下,二分查找的性能不一定好于普通的查找。

    五 诗词鉴赏

    满庭芳·赠于瓦罐先生
    
    朝代:[元代] 
          作者:[马钰] 
    
    有荣有辱,有利有害。有喜有忧相待。
    有得有失,自是有成有败。
    
    为人有生有死,但有形、必然有坏。休著有,自古来著,有有谁存在。
    好认无为无作,道无情无念,无憎无爱。
    
    无我无人无染,无著无碍。
    无心有消业障,这无无、人还悟解。
    无中趣,得无生无灭,超越三界。
    

    相关文章

      网友评论

          本文标题:一段二分查找代码

          本文链接:https://www.haomeiwen.com/subject/eiegnktx.html