go-遍歷文件夾及文件夾下文件比較工具總結


需求背景

公司某系統服務無法啟動,項目下文件夾中內容可能出現變動.此文件夾大小約3G.經乙方確認,需要從備份系統中還原,還原后系統啟動正常,經乙方核查,結論為改程序文件夾下的有些文件發生過變動,但不知道此時文件夾與備份的文件夾中有哪些文件發生過變化,於是需要寫一個比較工具.剛好最近在看go,於是用go寫個工具試試.這里就先叫file-compare-tool吧

涉及知識點

go-基礎部分:

  • go基礎環境配置,gopath等概念和配置
  • 基礎數據類型和類型轉換及變量初始化
  • 循環for用法、條件判斷if\switch用法
  • 數組和切片
  • Map
  • 字符串函數"strconv"與"strings"
  • defer函數
  • 面向對象-結構體定義,實體對象的創建和初始化
  • 空接口
  • panic的錯誤處理,panic與os.Exit的區別
  • go的包管理工具,這里用的是dep.go的包依賴.
  • go的協程機制
  • go的CSP並發機制,channel的初始化及使用
  • go的等待組sync.WaitGroup
  • go的文件的讀寫
  • go的json、md5、time的使用

go-第三方包:

  • go命令行工具go-flags

實現思路

既然要比較文件夾文件內容,所以初步篩選的思路如下:

  1. 遍歷指定的文件夾A(失效程序的文件夾)與文件夾B(備份系統還原出來的有效程序的文件夾)
  2. 根據文件的屬性,生成一個MD5的值,來作為文件的hashkey標識文件,然后分別輸入一個日志,記錄文件的hashkey和文件完整路徑.因為遍歷的根目錄名稱不同,所以初步“替換掉跟路徑的文件路徑+文件名+文件大小”作為值並生成hashkey
  3. 比較兩個日志文件中的hashkey,並取全部的差集,生成日志文件.這里如果hashkey不同,則認為兩個文件有差異.生成比較日志時,需獲取文件的名稱、大小、最后修改時間等屬性
  4. 考慮遍歷時需要遞歸,使用協程遞歸的速度還是比較快的,同時主要效率瓶頸在寫日志,於是使用多個協程即等待組的方式進行文件日志寫入.
  5. 對於用戶使用,采用命令行方式,並使用指定參數,來運行工具

主要代碼

遍歷文件夾生成文件日志

這里采用等待組方式,先開啟一個協程來遞歸遍歷文件夾,然后將獲取的文件信息放到一個channel中,當channel中獲取數據后,向日志文件中寫入數據.
初始化執行代碼如下:

func (p *InitCommand) Execute(args []string) error {
	logname := tools.CreateFileItemsLogFile("fileinfo ")
	fmt.Printf("create logfile: %s done,begin traverse files \n", logname)
	start := time.Now()

	var wg sync.WaitGroup
    //開一個協程進行讀取文件夾及子文件夾內文件
	writeCh := tools.AsyncFileItemService(p.CliTraverseRootDir, &wg, p.CliFileHashRule)
	for i := 0; i < p.CliWgNum; i++ {
		wg.Add(1)
        //讀取並日志文件
		tools.FileItemDataReceiver(logname, writeCh, &wg)
	}
	wg.Wait()
	elapsed := time.Since(start)
	fmt.Printf("all files done,logname: %s. time-consuming:%s \n", logname, elapsed)
	return nil
}

//開一個協程進行讀取文件夾及子文件夾內文件
func AsyncFileItemService(rootpath string, wg *sync.WaitGroup, fileHashRule string) chan FileHashInfo {
	retCh := make(chan []FileHashInfo, 1)
	fileHashInfoArray := [] FileHashInfo{}
	go func() {
        //遍歷文件夾
		ret := FileItemsDataProducer(rootpath, rootpath, fileHashInfoArray, fileHashRule)
		retCh <- ret
	}()
	fileHashInfoArray = <-retCh
	writeCh := make(chan FileHashInfo)
	wg.Add(1)
	go func() {
		for _, v := range fileHashInfoArray {
			writeCh <- v
		}
		close(writeCh)
		wg.Done()
	}()
	return writeCh
}

//遍歷文件夾
func FileItemsDataProducer(rootPrefix string, rootpath string, fileHashInfoArray []FileHashInfo, fileHashRule string) []FileHashInfo {
	dir, err := ioutil.ReadDir(rootpath)
	if err != nil {
		panic(errors.New("ReadDir error"))
	}
	pthSep := string(os.PathSeparator)

	var hashkey string
	fileHashInfo := FileHashInfo{}
	for _, itm := range dir {
		if itm.IsDir() {
			newPath := rootpath + pthSep + itm.Name()
			fileHashInfoArray = FileItemsDataProducer(rootPrefix, newPath, fileHashInfoArray, fileHashRule)
		} else {
			fileHashInfo.FilePath = rootpath + pthSep + itm.Name()
			switch fileHashRule {
			case "1100":
				temp_path := rootpath + pthSep + itm.Name()
				hashkey = strings.Replace(temp_path, rootPrefix, "", 1)
			case "1111":
				temp_path := rootpath + pthSep + itm.Name()
				temp_path = strings.Replace(temp_path, rootPrefix, "", 1)
				hashkey = temp_path + strconv.FormatInt(itm.Size(), 10) + strconv.Itoa(itm.ModTime().Second())
			//default 1110
			default:
				//將文件d:\1\2\3\4.txt 替換為1\2\3\4.txt 其中d:\為用戶傳入的根路徑
				temp_path := rootpath + pthSep + itm.Name()
				temp_path = strings.Replace(temp_path, rootPrefix, "", 1)
				//替換掉跟路徑的目錄后的 路徑及文件名及大小作為hash
				hashkey = temp_path + strconv.FormatInt(itm.Size(), 10)
				//fmt.Printf("rootpath: %s, itmName: %s, temppath:%s ,hashkey:%s \n",rootpath,itm.Name(),temp_path,hashkey)
			}

			Md5Inst := md5.New()
			Md5Inst.Write([]byte(hashkey))
			hashResult := Md5Inst.Sum(nil)
			fileHashInfo.HashKey = hex.EncodeToString(hashResult)
			fileHashInfoArray = append(fileHashInfoArray, fileHashInfo)
		}
	}
	return fileHashInfoArray
}

        
//讀取並日志文件
func FileItemDataReceiver(outlogname string, fch chan FileHashInfo, wg *sync.WaitGroup) {
	go func() {
		for {
			if data, ok := <-fch; ok {
				//fmt.Println(data)
				FileItemToLog(outlogname, data)
			} else {
				break
			}
		}
		wg.Done()
	}()
}

生成2個遍歷的日志文件后,對日志文件進行比較

/**
執行對比文件
*/
func ExecDifferenceFileHashInfo(filelogMap1 map[string]string, repPathPrefix1 string, filelogMap2 map[string]string, repPathPrefix2 string, logpath string) {
	retCh1 := make(chan FileHashInfo, 1)
	retCh2 := make(chan FileHashInfo, 1)
	var wg sync.WaitGroup
	DifferenceFileHash(filelogMap1, filelogMap2, retCh1, &wg)
	DifferenceFileHash(filelogMap2, filelogMap1, retCh2, &wg)
	combineDifferenceFileHash(&wg, retCh1, repPathPrefix1, logpath)
	combineDifferenceFileHash(&wg, retCh2, repPathPrefix2, logpath)
	wg.Wait()
}

/**
獲取兩個日志文件不同的內容
取filelogMap1里有而filelogMap2里沒有的
*/
func DifferenceFileHash(filelogMap1 map[string]string, filelogMap2 map[string]string, fch chan FileHashInfo, wg *sync.WaitGroup) {
	var retValue [] FileHashInfo
	fileHashInfo := FileHashInfo{}
	wg.Add(1)
	go func() {
		for k, v := range filelogMap1 {
			if _, ok := filelogMap2[k]; ok {
				continue
			} else {
				fileHashInfo.HashKey = k
				fileHashInfo.FilePath = v
				retValue = append(retValue, fileHashInfo)
				fch <- fileHashInfo
			}
		}
		close(fch)
		wg.Done()
	}()
}

/**
合並文件差集並寫入日志
*/
func combineDifferenceFileHash(wg *sync.WaitGroup, inCh chan FileHashInfo, pathPrefix string, outlogpath string) {
	wg.Add(1)
	go func() {
		for {
			if data, ok := <-inCh; ok {
				fileInfo := GetFileInfo(pathPrefix, data)
				FileItemToLog(outlogpath, fileInfo)
			} else {
				break
			}
		}
		wg.Done()
	}()
}

問題總結

  • go的語法和功能還是不熟練,挺多地方使用的不得當.
  • 對協程和調度不熟練,因為在做java時並發這塊用的就比較少,還需要加強.
  • 對於功能設計的還不夠完整,代碼結構也有問題.
  • 后續補充一下使用的基礎和第三方go-flags的知識總結
  • 感覺其實挺糟爛的

github源碼地址

版權聲明: 本文為 博客園 作者【學業未成】的原創文章。
原文鏈接:【https://www.cnblogs.com/GYoungBean/p/13871658.html】
文章轉載請聯系作者。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM