go語言path/filepath包之Walk源碼解析

go語言的path/filepath包包提供了很多兼容各個操作系統的文件路徑實用操作方法,今天只來看看Walk方法:


  • Walk(root stirng, walkFn WalkFunc) error          


該方法主要用於遞歸遍歷目錄:


walk方法會遍歷root下的所有文件(包含root)並對每一個目錄和文件都調用walkFunc方法。在訪問文件和目錄時發生的錯誤都會通過error參數傳遞給WalkFunc方法。文件是按照詞法順序進行遍歷的,這個通常讓輸出更漂亮,但是也會導致處理非常大的目錄時效率會降低。另外,Walk函數不會遍歷符號鏈接。


方法名 定義
WalkFunc type WalkFunc func(path string, info os.FileInfo, err error) error
Walk func Walk(root string, walkFn WalkFunc) error


  • type WalkFunc func(path string, info os.FileInfo, err error)      


函數根據文件信息path和info進行自定義操作:


WalkFunc是一個方法類型,Walk函數在遍歷文件或者目錄時調用。調用時將參數傳遞給path,將Walk函數中的root作爲前綴。將root + 文件名或者目錄名作爲path傳遞給WalkFunc函數。例如在"dir"目錄下遍歷到"a"文件,則path="dir/a";Info是path所指向文件的文件信息。如果在遍歷過程中出現了問題,傳入參數err會描述這個問題。WalkFunc函數可以處理這個問題,Walk將不會再深入該目錄。如果函數會返回一個錯誤,Walk函數會終止執行;只有一個例外,我們也通常用這個來跳過某些目錄。當WalkFunc的返回值是filepaht.SkipDir時,Walk將會跳過這個目錄,照常執行下一個文件。


  • Walk(root string, walkFn WalkFunc) 函數                                     


//這裏的參數root可以是文件名也可以是目錄名;walkFn是自定義的函數
func Walk(root string, walkFn WalkFunc) error {

   //獲取root的描述信息
   info, err := os.Lstat(root)
   if err != nil {
       //如果獲取描述信息發生錯誤,返回err由定義的walkFn函數處理
       err = walkFn(root, nil, err)
   } else {
       //調用walk(root, info, walkFn)函數進行遞歸遍歷root
       err = walk(root, info, walkFn)
   }
   if err == SkipDir {
       return nil
   }
   return err
}



  • func walk(path string, info os.FileInfo, walkFn WalkFunc) error 函數進行遞歸遍歷


func walk(path string, info os.FileInfo, walkFn WalkFunc) error {

   //調用定義的walkFn自定義函數處理
   err := walkFn(path, info, nil)
   if err != nil {
       //返回錯誤,且該目錄可以跳過
       if info.IsDir() && err == SkipDir {
           return nil
       }
       return err
   }

   //如果是文件,則遍歷下一個
   if !info.IsDir() {
       return nil
   }

   //讀取該path下的所有目錄和文件
   names, err := readDirNames(path)
   if err != nil {
       //發生錯誤,調用自定義函數處理
       return walkFn(path, info, err)
   }

   //遍歷文件和目錄列表
   for _, name := range names {
       //路徑path/name
       filename := Join(path, name)
       //獲取該文件或者目錄信息
       fileInfo, err := lstat(filename)
       if err != nil {
           //發生錯誤,調用自定義函數處理
           if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
               return err
           }
       } else {
           //這裏遞歸調用,獲取root下各級文件和目錄信息,在自定義函數walkFn裏做處理
           err = walk(filename, fileInfo, walkFn)
           if err != nil {
               //遍歷文件發生錯誤或者目錄發生錯誤且不能跳過,則返回err
               if !fileInfo.IsDir() || err != SkipDir {
                   return err
               }
           }
       }
   }
   return nil
}


實例演練:

 將一個目錄或者文件壓縮爲zip包:

func main() {
   //oldFileName可以是文件或者目錄
   oldFileName := "root.log"

   currentTime := time.Now()

   //獲取s
   mSecond := fmt.Sprintf("%03d", currentTime.Nanosecond() / 1e6)

   //zip文件名
   zipFileName := strings.Split(oldFileName, ".")[0] + "_" + currentTime.Format("20060102150405") + mSecond + ".zip"

   //壓縮文件
   zipFile(oldFileName, zipFileName)
}

func zipFile(source, target string) error{

   //創建目標zip文件
   zipFile , err := os.Create(target)

   if err != nil {
       fmt.Println(err)
       return err
   }

   //關閉文件
   defer zipFile.Close()

   //創建一個寫zip的writer
   archive := zip.NewWriter(zipFile)

   defer archive.Close()

   return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {

       if err != nil {
           return err
       }

       //將文件或者目錄信息轉換爲zip格式的文件信息
       header, err := zip.FileInfoHeader(info)

       if err != nil{
           return err
       }

       if !info.IsDir() {
           // 確定採用的壓縮算法(這個是內建註冊的deflate)
           header.Method = zip.Deflate
       }

       //
       header.SetModTime(time.Unix(info.ModTime().Unix(), 0))

       //文件或者目錄名
       header.Name = path

       //創建在zip內的文件或者目錄
       writer, err := archive.CreateHeader(header)

       if err != nil{
           return err
       }

       //如果是目錄,只需創建無需其他操作
       if info.IsDir() {
           return nil
       }

       //打開需要壓縮的文件
       file, err := os.Open(path)

       if err != nil{
           return err
       }

       defer file.Close()

       //將待壓縮文件拷貝給zip內文件
       _, err = io.Copy(writer, file)

       return err

   })
}

運行效果如下圖:


linux上解壓後的時間和原文件時間一樣

這種壓縮文件的方式避免了zip包在linux上解壓以後文件的修改時間爲1979年12月31日的問題。





package main

import (
   "time"
   "fmt"
   "strings"
   "os"
   "archive/zip"
   "io/ioutil"
   "path/filepath"
   "io"
)

func main() {

   //oldFileName可以是文件或者目錄
   oldFileName := "root.log"

   compressZip(oldFileName)
}

func compressZip(oldFileName string) string{

   fd, err := ioutil.ReadFile(oldFileName)

   if err != nil {
       fmt.Println("ReadFile ", oldFileName, "is error ", err)
       return ""
   }

   currentTime := time.Now()

   mSecond := fmt.Sprintf("%03d", currentTime.Nanosecond() / 1e6)

   zipFileName := strings.Split(oldFileName, ".")[0] + "_" + currentTime.Format("20060102150405") + mSecond + ".zip"

   fw, err := os.OpenFile(zipFileName, os.O_RDWR | os.O_CREATE | os.O_TRUNC, 400)

   if err != nil {
       fmt.Println("OpenFile ", zipFileName, "is error ", err)
       return ""
   }

   defer fw.Close()

   w := zip.NewWriter(fw)

   defer w.Close()

   f, err := w.Create(filepath.Base(oldFileName))

   if err != nil {
       fmt.Println("Create ", oldFileName, "is error ", err)
       return ""
   }

   _, err = f.Write(fd)

   if err != nil {
       fmt.Println("Write newFileName is error ", err)
       return ""
   }

   return zipFileName
}


以上這種方式壓縮的zip包在linux上解壓後時間爲1979年12月31日,如下圖。不利於生產環境問題定位。


解壓後時間不對

  

我是小碗湯,我們來一起學習。



掃碼關注我,你還在等什麼

本文分享自微信公衆號 - 我的小碗湯(mysmallsoup)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章