zoukankan      html  css  js  c++  java
  • 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】
    文章转载请联系作者。

  • 相关阅读:
    前端思想实现:面向UI编程_____前端框架设计开发
    使用单体模式设计原生js插件
    QQ空间首页背景图片淡出解析与不足完善
    网页字体设置
    Asp.net MVC Session过期异常的处理
    日本设计的七个原则
    断开所有远程连接(sql server)
    Ubuntu1404+Django1.9+Apache2.4部署配置2配置文件设置
    Linux系统查找文件find命令使用(不断更新)
    ubuntu1404下Apache2.4错误日志error.log路径位置
  • 原文地址:https://www.cnblogs.com/GYoungBean/p/13871658.html
Copyright © 2011-2022 走看看