網站首頁 編程語言 正文
前言
獲取目錄下匹配某種規則的文件,返回文件列表,在開發中比較常用。本文實現此功能,并做了些擴展。
起因
筆者開發的內部工具,需要查找各式文件,比如:
- 數據文件,以csv結尾;
- 信息文件,以md結尾(因為已有工具生成并渲染得html發布到網頁上,直接拿來使用);
- 壓縮包,以zip結尾。
由于設計原因,部分不同各類的文件會在同一目錄出現,因此,要實現一個接口用以獲取某個目錄下符合條件的文件。
設計思路如下:
- 為簡單化,不遞歸查找子目錄,當傳入的非目錄或不存在時,直接返回即可。
- 設計是否帶目錄標志,有時僅需文件名稱,而不需要其目錄。
- 設計返回文件列表數量,0表示所有文件,因此有時僅需要得到符合要求的第一個文件。
- 設計是否排序標志,因為默認是升序,所以該標志實際是降序標志。
實現
為減少篇幅,僅摘錄必要的源碼。
根據匹配字符串(為描述方便,稱之為patten
)的位置,需實現如下接口:
- GetFileListBySuffix:patten 作為后綴,可按后綴名匹配。通過strings.HasSuffix判斷。
- GetFileListByPrefix:同前,但是是前綴。通過strings.HasSuffix判斷。
- GetFileListByKey:同上,在任意位置出現均可。通過strings.HasPrefix判斷。
另外實現os.FileInfo
三個接口,達到降序排序目的。通過strings.Contains
判斷。
封裝
代碼如下:
// 按文件名排序,可擴展至文件時間 type byName []os.FileInfo //func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } // 文件名升序,默認方式 func (f byName) Less(i, j int) bool { return f[i].Name() > f[j].Name() } // 文件名倒序 func (f byName) Len() int { return len(f) } func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } // GetFileListBySuffix returns an ordered list of file paths. // It recognize if given path is a file, and don't do recursive find. func GetFileListBySuffix(dirPath, suffix string, needDir bool, isDescend bool, num int) ([]string, error) { if !IsExist(dirPath) { return nil, fmt.Errorf("given path does not exist: %s", dirPath) } else if IsFile(dirPath) { return []string{dirPath}, nil } // Given path is a directory. dir, err := os.Open(dirPath) if err != nil { return nil, err } fis, err := dir.Readdir(0) if err != nil { return nil, err } if isDescend { sort.Sort(byName(fis)) } if num == 0 { num = len(fis) } files := make([]string, 0, num) for i := 0; i < num; i++ { fi := fis[i] if strings.HasSuffix(fi.Name(), suffix) { if needDir { files = append(files, filepath.Join(dirPath, fi.Name())) } else { files = append(files, fi.Name()) } } } return files, nil } // as GetFileListBySuffix, but for Prefix func GetFileListByPrefix(dirPath, suffix string, needDir bool, isDescend bool, num int) ([]string, error) { if !IsExist(dirPath) { return nil, fmt.Errorf("given path does not exist: %s", dirPath) } else if IsFile(dirPath) { return []string{dirPath}, nil } // Given path is a directory. dir, err := os.Open(dirPath) if err != nil { return nil, err } fis, err := dir.Readdir(0) if err != nil { return nil, err } if isDescend { sort.Sort(byName(fis)) } if num == 0 { num = len(fis) } files := make([]string, 0, num) for i := 0; i < num; i++ { fi := fis[i] if strings.HasPrefix(fi.Name(), suffix) { if needDir { files = append(files, filepath.Join(dirPath, fi.Name())) } else { files = append(files, fi.Name()) } } } return files, nil } // 根據關鍵字查找 func GetFileListByKey(dirPath, key string, needDir bool, isDescend bool, num int) ([]string, error) { if !IsExist(dirPath) { return nil, fmt.Errorf("given path does not exist: %s", dirPath) } else if IsFile(dirPath) { return []string{dirPath}, nil } // Given path is a directory. dir, err := os.Open(dirPath) if err != nil { return nil, err } fis, err := dir.Readdir(0) if err != nil { return nil, err } if isDescend { sort.Sort(byName(fis)) } if num == 0 { num = len(fis) } files := make([]string, 0, num) for i := 0; i < num; i++ { fi := fis[i] if strings.Contains(fi.Name(), key) { if needDir { files = append(files, filepath.Join(dirPath, fi.Name())) } else { files = append(files, fi.Name()) } } } return files, nil }
測試及結果
測試用到的目錄及文件如下:
$ tree foo/
foo/
|-- bar_1.json
|-- bar_10.json
|-- bar_2.json
|-- bar_20.json
|-- bar_3.json
|-- log.1
|-- log.10
|-- log.2
|-- log.20
`-- log.30 directories, 10 files
前綴匹配調用:
list, err1 := GetFileListByPrefix("./foo", "log", true, false, 0) fmt.Println(list, err1)
結果:
[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>
后綴匹配調用:
list, err1 = GetFileListBySuffix("./foo", "json", true, false, 0) fmt.Println(list, err1)
結果:
[foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>
一般匹配調用:
list, err1 = GetFileListByKey("./foo", "1", true, false, 0) fmt.Println(list, err1)
結果:
[foo\bar_1.json foo\bar_10.json foo\log.1 foo\log.10] <nil>
從結果分析,符合預期。
擴展
優化接口
從前面封裝的接口代碼分析,絕大部分代碼是相同的,僅僅是讀取后對文件名稱的判斷不同而已。因此,可以將它們合成一個接口。設計如下:
-
patten
額外添加*
,通過*
位置匹配前綴(foo*
)、后綴(*foo
)、中間(*foo*
,即包含該關鍵字)。 - 在內部判斷并去掉
*
。其余機制同前。
代碼如下:
func GetFileListByKey_new(dirPath, patten string, needDir bool, isDescend bool, num int) ([]string, error) { if !IsExist(dirPath) { return nil, fmt.Errorf("given path does not exist: %s", dirPath) } else if IsFile(dirPath) { // 如果是文件,返回該文件 return []string{dirPath}, nil } if len(patten) < 2 { return nil, fmt.Errorf("given patten len < 2") } // 默認為中間 pos := 3 rpatten := patten if patten[0] == '*' && patten[len(patten)-1] == '*' { // 一般 rpatten = patten[1 : len(patten)-1] pos = 3 } else if patten[0] == '*' { // 后綴 pos = 1 rpatten = patten[1:] } else if patten[len(patten)-1] == '*' { // 前綴 rpatten = patten[:len(patten)-1] pos = 2 } // Given path is a directory. dir, err := os.Open(dirPath) if err != nil { return nil, err } fis, err := dir.Readdir(0) if err != nil { return nil, err } fnum := len(fis) if fnum == 0 { return []string{}, nil } if isDescend { sort.Sort(byName(fis)) } if num == 0 { num = fnum } else { if num > fnum { // 如指定數量多于實際的,則使用實際 num = fnum } } files := make([]string, 0, num) checkFile := func(filename string) bool { if pos == 1 { return strings.HasSuffix(filename, rpatten) } else if pos == 2 { return strings.HasPrefix(filename, rpatten) } else if pos == 3 { return strings.Contains(filename, rpatten) } return true } for i := 0; i < num; i++ { fi := fis[i] if checkFile(fi.Name()) { if needDir { files = append(files, filepath.Join(dirPath, fi.Name())) } else { files = append(files, fi.Name()) } } } return files, nil
調用如下:
list, err1 = GetFileListByKey_new("./foo", "log*", true, false, 0) fmt.Println(list, err1) list, err1 = GetFileListByKey_new("./foo", "*json", true, false, 0) fmt.Println(list, err1) list, err1 = GetFileListByKey_new("./foo/", "*1*", true, false, 0) fmt.Println(list, err1)
結果如下:
[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>
[foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>
[foo\bar_1.json foo\bar_10.json foo\log.1 foo\log.10] <nil>
正則表達式
雖然目前未使用到,但還是實現了按正則表達式來匹配文件名的接口,實現如下:
// 根據正規正則表達式查找 func GetFileListByPatten(dirPath, patten string, needDir bool, isDescend bool, num int) ([]string, error) { if !IsExist(dirPath) { return nil, fmt.Errorf("given path does not exist: %s", dirPath) } else if IsFile(dirPath) { return []string{dirPath}, nil } // Given path is a directory. dir, err := os.Open(dirPath) if err != nil { return nil, err } fis, err := dir.Readdir(0) if err != nil { return nil, err } fnum := len(fis) if fnum == 0 { return []string{}, nil } if isDescend { sort.Sort(byName(fis)) } if num == 0 { num = fnum } else { if num > fnum { // 如指定數量多于實際的,則使用實際 num = fnum } } match := func(str string, patten string) bool { rep := regexp.MustCompile(patten) ret := rep.MatchString(str) return ret } files := make([]string, 0, num) for i := 0; i < num; i++ { fi := fis[i] if match(fi.Name(), patten) { if needDir { files = append(files, filepath.Join(dirPath, fi.Name())) } else { files = append(files, fi.Name()) } } } return files, nil }
調用如下:
list, err1 = GetFileListByPatten("./foo", `log.+`, true, false, 0) fmt.Println(`log.+ : `, list, err1) list, err1 = GetFileListByPatten("./foo/", `[a-z0-9_]*.json`, true, false, 0) fmt.Println(`[a-z0-9_]*.json : `, list, err1)
結果如下:
log.+ : ?[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>
[a-z0-9_]*.json : ?[foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>
說明:正則表達式一定要符合要求,否則不識別。
帶數字的文件名稱排序
前面的測試結果:
[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3]
可以看到,文件的排序不是我們想象中那樣的順序,實際上,從字符串比較(因為代碼就是使用這個方式)的角度看,上述排序是正常的。如要達到想象的順序,則要修改比較方式,重新實現os.FileInfo
中的Less
接口。
因為帶數字的文件名稱有各式各樣,此處假定按文件的“后綴名”數字由大到小排序。實際上,這是真實的的場合:某工程程序產生的日志,按超過指定大小會自動創建新的文件,后綴帶數字即表達有多少個日志文件。
代碼如下:
// 按帶數字的文件名排序 type byNumericalFilename []os.FileInfo func (f byNumericalFilename) Len() int { return len(f) } func (f byNumericalFilename) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func (f byNumericalFilename) Less(i, j int) bool { pathA := f[i].Name() pathB := f[j].Name() // !! 根據需求,文件最后是數字,按其值降序排序,示例:ddd.log.x.1 ddd.log.x.2 // 如有其它者,也可以修改 a, err1 := strconv.Atoi(pathA[strings.LastIndex(pathA, ".")+1:]) b, err2 := strconv.Atoi(pathB[strings.LastIndex(pathB, ".")+1:]) // 整體文件(不含后綴名)名稱排序,名稱是數字,如1.txt 2.txt 10.txt // a, err1 := strconv.Atoi(pathA[0:strings.LastIndex(pathA, ".")]) // b, err2 := strconv.Atoi(pathB[0:strings.LastIndex(pathB, ".")]) // 有錯誤,默認降序 if err1 != nil || err2 != nil { return pathA > pathB } // 按數字降序 return a > b } func getFileListByPrefix(dirPath, suffix string, needDir bool, num int) ([]string, error) { if !isExist(dirPath) { return nil, fmt.Errorf("given path does not exist: %s", dirPath) } else if isFile(dirPath) { return []string{dirPath}, nil } // Given path is a directory. dir, err := os.Open(dirPath) if err != nil { return nil, err } fis, err := dir.Readdir(0) if err != nil { return nil, err } fnum := len(fis) if fnum == 0 { return []string{}, nil } sort.Sort(byNumericalFilename(fis)) if num == 0 { num = fnum } else { if num > fnum { num = fnum } } files := make([]string, 0, num) for i := 0; i < num; i++ { fi := fis[i] if strings.HasPrefix(fi.Name(), suffix) { if needDir { files = append(files, filepath.Join(dirPath, fi.Name())) } else { files = append(files, fi.Name()) } } } return files, nil }
調用如下:
list, err1 = getFileListByPrefix("./foo/", `log`, true, 0) fmt.Println(`sort number : `, list, err1)
結果如下:
sort number : ?[foo\log.20 foo\log.10 foo\log.3 foo\log.2 foo\log.1] <nil>
從結果看,已實現根據數字從大到小排序。
總結
文中所實現的接口,已應用到筆者實際的工程。目前未發現有什么問題。
隔了一天的PS:
由于傳遞參數可以指定文件數量,當指定的數量多了實際數量時,會報錯,需要加上判斷。在“優化接口”及后面小節的代碼中已修正,前面由于實際中不使用,故保留。
有時話真的不是說得太滿。記之并以此為鑒。
原文鏈接:https://blog.csdn.net/subfate/article/details/127463747
相關推薦
- 2022-12-13 C++?Boost?Pool超詳細講解_C 語言
- 2022-09-27 React?函數式組件和類式組件詳情_React
- 2023-01-12 pandas中的DataFrame數據遍歷解讀_python
- 2022-06-17 GO語言協程互斥鎖Mutex和讀寫鎖RWMutex用法實例詳解_Golang
- 2022-08-23 自學python求已知DNA模板的互補DNA序列_python
- 2022-12-07 C#?XML文件操作之相機參數保存和讀取_C#教程
- 2023-01-05 Kotlin?object的幾種用法示例詳解_Android
- 2022-02-01 for循環中嵌套異步請求導致順序錯亂
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支