go-遍历文件夹及文件夹下文件比较工具总结

需求背景

公司某系统服务无法启动,项目下文件夹中内容可能出现变动.此文件夹大小约3G.经乙方确认,需要从备份系统中还原,还原后系统启动正常,经乙方核查,结论为改程序文件夹下的有些文件发生过变动,但不知道此时文件夹与备份的文件夹中有哪些文件发生过变化,于是需要写一个比较工具.刚好最近在看go,于是用go写个工具试试.这里就先叫file-compare-tool吧

涉及知识点

go-基础部分:

  • go基础环境配置,gopath等概念和配置
  • 基础数据类型和类型转换及变量初始化
  • 循环for用法、条件判断ifswitch用法
  • 数组和切片
  • 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 
", 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 
", 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:1234.txt 替换为1234.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 
",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】
文章转载请联系作者。

原文地址:https://www.cnblogs.com/GYoungBean/p/13871658.html