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】

文章转载请联系作者。