go语言并发文件备份,自动比对自动重命名(逐行注释)

主要功能:

  • 输入操作(用户输入):
    • 输入源文件夹地址,目标文件夹地址,同步协程数。
  • A操作(添加任务数据):
    • 一个协程程序读取源文件夹下所有文件绝对路径,生成相应的目标文件夹下的绝对路径。
    • 将源文件绝对路径和目标文件绝对路径,存储在数据队列中。
  • B操作(读取数据,并发比对备份文件):
    • 通过循环判断数据队列中是否有数据,或者是否添加数据完成。如果没数据,同时B操作添加数据已完成,退出循环(退出C操作)。
    • 如果添加数据未完成,循环读取数据队列中的值,如果有数据,按照输入的同步协程数,同步读取队列中数据,进行如下操作:
      • 目标已经有重名文件,比对文件的md5和sha256(防止哈希碰撞),如果相同,证明文件相同,此文件不做任何操作,退出本次循环。
      • 如果md5和sha256有不同,证明两文件只是文件名相同,文件内容有不同,对目标文件重命名,如果重命名后,继续循环上一操作,直到目标中没有重命名后的文件。(重命名就是对原文件名后加“(1)”,多次重命名后就是:“文件名(1)(1)(1).扩展名”)
      • 目标没有重名文件,或者重命名完成后的文件,备份文件。

代码缺点

  • 问题1:数据队列没有数据时,消费者需要不停的循环判断等待。
  • 希望有解决方案:改为堵塞等待,性能会有进一步提升。
  • 问题2:if wq.Size() > 0 这行代码没法保证后续的操作确实大于零,因为判断同时,其他读取操作也在进行中,可能判断完了,队列就为空了,很容易读到空值。
  • 欢迎提出解决方案。

主文件代码

package mainimport ("fmt""io/fs""os""path/filepath""sync"
)
// 全局变量
var wq = NewWorkQueue()               // 数据队列
var fz = false                        // 添加任务完毕后,设置为true
var wgroup = sync.WaitGroup{}         // 用于同步等待协程完成
var readMax = 5                       // 一次最多获取数据量
var ch = make(chan struct{}, readMax) // 控制获取数据量
var cn = NewCountNum()                // 创建计数器,用于记录成功复制次数
func main() {var srcDir, dstDir stringfmt.Print("源文件路径:")fmt.Scanln(&srcDir)fmt.Print("目标文件路径:")fmt.Scanln(&dstDir)srcAbs, _ := filepath.Abs(srcDir) // 源目录绝对路径dstAbs, _ := filepath.Abs(dstDir) // 目标目录绝对路径fmt.Print("同步数量:")fmt.Scanln(&readMax)wgroup.Add(1) // 添加数据协程+1go A(srcAbs, dstAbs)B()           // 动态获取数据wgroup.Wait() // 等待协程完成logstr := fmt.Sprintf("[完成] %s %s 错误:%d,已存在:%d,成功:%d!\n", srcAbs, dstAbs, cn.ErrGet(), cn.WarnGet(), cn.OkGet())SaveLog(logstr)
}// 动态添加数据
func A(srcAbs, dstAbs string) {os.MkdirAll(dstAbs, os.ModeDir) // 目录不存在时,创建filepath.WalkDir(srcAbs, func(path string, d fs.DirEntry, err error) error {if err != nil {return err}dstPath := DstPaht(path, srcAbs, dstAbs) // 目标文件绝对路径if d.IsDir() {                           // 是目录os.MkdirAll(dstPath, os.ModeDir) // 目标目录不存在时,创建} else { // 不是目录wq.Add(map[string]string{"src": path, "dst": dstPath})}return nil})fz = true     // 添加数据完成,告知数据获取协程wgroup.Done() // 添加数据完成
}// 动态获取数据
func B() {for {if fz && wq.Size() == 0 { // 添加数据已完成,并且数据链长度为0return // 退出获取数据操作} // 添加数据已完成,并且队列为空时,退出获取数据if wq.Size() > 0 { // 数据链上有数据节点go func() {defer wgroup.Done() // 完成后,协程计数-1wgroup.Add(1)       // 协程计数+1,防止退出data := wq.Pop()    // 从数据队列取出一个数据if data != nil {    // 数据存在时(因判断队列长度到取出数据过程中可能有其他协程取走数据,导致获取到空值)// 从数据队列获取数据,通过信号量控制并发数量ch <- struct{}{}            // 获取信号量,占用一个并发资源,满时等待任务释放后继续执行wgroup.Add(1)               // 协程计数+1,防止退出go func(data interface{}) { // 参数为源文件路径和目标文件路径defer func() {<-ch          // 任务完成释放信号量,归还并发资源wgroup.Done() // 完成后,协程计数-1}()val, _ := data.(map[string]string) // 将interface{}数据转换为map数据srcPath := val["src"]              // 源文件路径dstPath := val["dst"]              // 目标文件路径for IsFileExist(dstPath) {         // 目标文件存在// 判断md5,sha256是否相同srcMd5, srcSha256, _ := FileHash(srcPath)dstMd5, dstSha256, _ := FileHash(dstPath)if srcMd5 == dstMd5 && srcSha256 == dstSha256 { // md5和sha256都相同,防止产生哈希碰撞cn.WarnAdd() // 目标文件已存在,计数器+1fmt.Print("\r"+dstPath, " -> 已存在!")return // 目标文件存在,不用复制文件} else {dstPath = FileRename(dstPath) // 目标文件存在,但是内容不一样,重命名保存}}CopyFile(srcPath, dstPath)         // 复制文件(目标文件不存在时直接复制,目标文件不一样时,重命名后复制)cn.OkAdd()                         // 复制文件成功,复制计数器+1fmt.Print("\r"+dstPath, " -> 完成!") // 完成一个文件复制}(data)}}()}}
}// 用于统计成功的数量和已存在的数量
// 变更数值时加锁,防止数据错误
type CountNum struct {okNum   int // 成功复制统计数warnNum int // 目标文件已存在数errNum  int // 错误数mutex   sync.Mutex
}func NewCountNum() *CountNum {return &CountNum{okNum: 0, warnNum: 0, errNum: 0, mutex: sync.Mutex{}}
}
func (cn *CountNum) OkAdd() {cn.mutex.Lock()defer cn.mutex.Unlock()cn.okNum++
}
func (cn *CountNum) OkGet() int {return cn.okNum
}
func (cn *CountNum) WarnAdd() {cn.mutex.Lock()defer cn.mutex.Unlock()cn.warnNum++
}
func (cn *CountNum) WarnGet() int {return cn.warnNum
}
func (cn *CountNum) ErrAdd() {cn.mutex.Lock()defer cn.mutex.Unlock()cn.errNum++
}
func (cn *CountNum) ErrGet() int {return cn.errNum
}

文件、目录操作代码

package mainimport ("crypto/md5""crypto/sha256""encoding/hex""fmt""io""log""os""path/filepath""strings"
)// 根据源文件路径,生成目标文件路径
// 源文件绝对路径,源文件根目录,目标文件根目录->目标文件绝对路径
func DstPaht(srcPath, srcAbs, dstAbs string) string {return strings.Replace(srcPath, srcAbs, dstAbs, -1)
}// 文件重命名
// 接收一个路径,或者文件名,返回不带路径重命名后的文件名
// 返回格式:原文件名(1).扩展名
func FileRename(filePath string) string {nameExt := filepath.Ext(filePath)                  // 文件扩展名,最后一个点后的字符串,包括点nameSrart := strings.TrimSuffix(filePath, nameExt) // 去除文件扩展名后的绝对路径return fmt.Sprint(nameSrart, "(1)", nameExt)       // 新文件名加:(1)
}// 返回文件md5、sha256、错误
// 输入文件路径
func FileHash(path string) (string, string, error) {f, err := os.Open(path) // 打开文件if err != nil {return "", "", err}defer f.Close()h5 := md5.New()                           // 创建md5if _, err := io.Copy(h5, f); err != nil { // 将文件拷贝到md5return "", "", err}h256 := sha256.New()                        // 创建sha256f.Seek(0, 0)                                // 将文件指针指向开始位置if _, err := io.Copy(h256, f); err != nil { // 将文件拷贝到sha256return "", "", err}return hex.EncodeToString(h5.Sum(nil)), hex.EncodeToString(h256.Sum(nil)), err
}// 文件是否存在
func IsFileExist(path string) bool {_, err := os.Stat(path)if err != nil { //文件不存在return false}return true
}// 文件复制
func CopyFile(src, dst string) {rFile, err := os.Open(src) // 源文件defer rFile.Close()if err != nil {log := "[err-a] " + src + " 空 读取源文件错误"cn.ErrAdd() // 错误,计数器+1SaveLog(log)}wFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0777) // 目标文件defer wFile.Close()if err != nil {log := "[err-b] 空 " + dst + " 创建目标文件错误"cn.ErrAdd() // 错误,计数器+1SaveLog(log)}_, err = io.Copy(wFile, rFile) // 复制文件if err != nil {log := "[err-c] " + src + " " + dst + " 复制错误"cn.ErrAdd() // 错误,计数器+1SaveLog(log)}
}// 数据存数据库或文件
// 存储错误日志
func SaveLog(loginfo string) {file, _ := os.OpenFile("run.Log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) // 打开文件defer file.Close()logger := log.New(file, "", log.Ldate|log.Ltime)logger.Println(loginfo)// 修改日志配置logger.SetOutput(os.Stdout)logger.Print("\r" + loginfo)
}

数据队列代码

package mainimport ("sync"
)// 数据队列以链表的形式存储数据,每个节点存储一个任意类型的数据,
// 创建数据队列、添加数据、删除数据、获取队列长度,每个数据存储在一个节点中。
// 先进先出// 数据节点
type DataNode struct {data interface{} // 节点中的数据next *DataNode   // 指向下一个节点
}// 数据队列,存贮数据节点
type WorkQueue struct {root  *DataNode  // 头结点size  int        // 队列长度mutex sync.Mutex // 锁
}// 创建数据队列
func NewWorkQueue() *WorkQueue {wq := &WorkQueue{root: nil, size: 0}return wq
}// 数据入队
// 切片数据:wq.Add([]string{"aaa", "bbb"})
// 字符串数据:wq.Add("ccc")
// 字典数据:wq.Add(map[string]string{"a": "aa", "b": "bb"})
func (wq *WorkQueue) Add(data interface{}) {wq.mutex.Lock()         // 加锁defer wq.mutex.Unlock() // 解锁if wq.root == nil {     // 队列为空wq.root = new(DataNode) // 创建节点,赋值给头节点wq.root.data = data     // 节点数据赋值} else {dn := new(DataNode)    // 创建节点dn.data = data         // 节点数据赋值node := wq.root        // 获取队列头节点for node.next != nil { // 从头节点开始向下寻找尾节点node = node.next // 有下一个节点时,将下一个节点置为当前节点}node.next = dn // 将新节点连接到最后一个节点位置}wq.size++ // 数据队列长度+1
}// 获取队首数据,并从队列中删除节点。
// 返回数据可能为nil,使用前需判断过滤nil值。
func (wq *WorkQueue) Pop() interface{} {wq.mutex.Lock()         // 加锁defer wq.mutex.Unlock() // 解锁if wq.root == nil {     // 数据队列为空return nil} else {node := wq.root     // 获取首节点v := node.data      // 获取首节点数据wq.root = node.next // 首节点设置为第二个节点wq.size--           // 数据队列长度-1return v            // 返回首节点数据}
}// 获取数据队列长度
func (wq *WorkQueue) Size() int {return wq.size
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/495099.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring常见面试题总结

关于详细介绍&#xff0c;可以看我写的 ( Spring知识点) 这篇文章。 Spring 基础 什么是 Spring 框架? Spring 是一款开源的轻量级 Java 开发框架&#xff0c;旨在提高开发人员的开发效率以及系统的可维护性。 我们一般说 Spring 框架指的都是 Spring Framework&#xff0c…

Mac系统下 IDEA配置Maven本地仓库

1.为什么需要配置本地仓库&#xff1f; 在软件开发过程中&#xff0c;使用Maven工具进行依赖管理是常见的做法。Maven通过集中管理各种依赖库&#xff0c;能够帮助开发者在项目中轻松地引入所需的第三方库&#xff0c;并确保项目能够顺利构建和部署。然而&#xff0c;在使用Mav…

RGCL:A Review-aware Graph Contrastive Learning Framework for Recommendation

A Review-aware Graph Contrastive Learning Framework for Recommendation 解决的问题 基于评论的推荐可以自然地形成为具有来自相应用户项目评论的边特征的用户项目二分图。那么就可以利用评论感知图中独特的自监督信号来指导推荐的两个组件:用户-项目嵌入学习,用户-项目…

5、mysql的读写分离

主从复制 主从复制的含义 主从复制&#xff1a;在一个mysql的集群当中&#xff0c;至少3台&#xff0c;即主1台&#xff0c;从2台。 当有数据写入时&#xff0c;主负责写入本库&#xff0c;然后把数据同步到从服务器。 一定是在主服务器写入数据&#xff0c;从服务器的写入…

重生之我在异世界学编程之C语言:深入预处理篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、预处理的作用与流程&#xf…

信创源代码加密的答案:信创沙箱

在信息化与工业化融合创新&#xff08;信创&#xff09;的背景下&#xff0c;企业面临着前所未有的数据安全挑战。SDC沙盒技术以其独特的隔离和保护机制&#xff0c;为信创环境提供了强有力的支持。以下是SDC沙盒在信创支持方面的优势&#xff0c;这些优势体现了其在保护企业数…

计算机网络B重修班-期末复习

[TOC] (计算机网络B重修班-期末复习&#xff09; 一、单选 &#xff08;20题&#xff0c;1分/题&#xff0c;共20分&#xff09; 二、判断 &#xff08;10题&#xff0c;1分/题&#xff0c;共10分&#xff09; 三、填空 &#xff08;10题&#xff0c;1分/题&#xff0c;共10…

结合实例从HCI层分析经典蓝牙连接和配对过程

我们知道&#xff0c;经典蓝牙BREDR的link key协商是在LMP层做的&#xff0c;那么蓝牙Host在鉴权的过程中&#xff0c;会跟BT SOC有哪些交互&#xff1a; 首次配对 在HCI Inuqiry找到想要配对的设备后&#xff0c;Host会调用HCI Create Connection命令去连接对方设备&#xf…

StartAI图生图局部重绘,让画面细节焕发新生!!

在设计的世界里&#xff0c;每一个细节都承载着我们的创意与心血。然而&#xff0c;有时我们总会遇到一些不尽如人意的画面细节&#xff0c;它们如同瑕疵般破坏了整体的和谐与美感。今天&#xff0c;我要向大家推荐一款强大的工具——StartAI的局部重绘功能&#xff0c;它正是我…

VMware vCenter保姆级安装部署(VMware VCenter Nanny Level Installation and Deployment)

VMware vCenter保姆级安装部署教程 VMware vCenter‌是由VMware开发的一款虚拟化管理平台&#xff0c;主要用于管理和监控虚拟化环境中的虚拟机、主机和存储资源。它提供了一个集中控制的平台&#xff0c;简化了虚拟化基础设施的管理工作&#xff0c;提高了资源利用率和灵活性…

蓝牙协议——音量控制

手机设置绝对音量 使用Ellisys查看如下&#xff1a; 使用Wireshark查看如下&#xff1a; 音量的量程是128&#xff0c;0x44的十进制是68&#xff0c;53%或54%音量的计算如下&#xff1a; 68 / 128 53.125%耳机设置绝对音量

如何完全剔除对Eureka的依赖,报错Cannot execute request on any known server

【现象】 程序运行报错如下&#xff1a; com.netflix.discovery.shared.transport.TransportException报错Cannot execute request on any known server 【解决方案】 &#xff08;1&#xff09;在Maven工程中的pom去掉Eureka相关的引用&#xff08;注释以下部分&#xff0…

从AI换脸到篡改图像,合合信息如何提升视觉内容安全?

本文目录 引言一、AI“真假之战”下的发展现状与考验挑战1.1 视觉内容安全现状与技术分类1.2视觉内容安全企业1.3视觉内容安全领域挑战 二、开山之石&#xff1a;引领视觉内容安全的创新之路2.1合合内容安全系统2.2发起编制相关技术规范2.3参与篡改检测挑战赛 三、视觉内容安全…

虚幻引擎结构之ULevel

在虚幻引擎中&#xff0c;场景的组织和管理是通过子关卡&#xff08;Sublevel&#xff09;来实现的。这种设计不仅提高了资源管理的灵活性&#xff0c;还优化了游戏性能&#xff0c;特别是在处理大型复杂场景时。 1. 场景划分模式 虚幻引擎采用基于子关卡的场景划分模式。每个…

log4j2漏洞复现(CVE-2021-44228)

靶场环境 步骤一&#xff1a;设置出战规则 步骤二&#xff1a;开启靶场 cd vulhub cd log4j cd CVE-2021-44228 docker-compose up -d docker ps 访问端口 靶机开启 步骤三&#xff1a;外带注入 获得dnslog 靶机访问dnslog 得到dnslog的二级域名信息 步骤四&#xff1a;构造…

美国加州房价数据分析01

1.项目简介 本数据分析项目目的是分析美国加州房价数据&#xff0c;预测房价中值。 环境要求&#xff1a; ancondajupyter notebookpython3.10.10 虚拟环境&#xff1a; pandas 2.1.1 numpy 1.26.1 matplotlib 3.8.0 scikit-learn1.3.1 2. 导入并探索数据集 通用的数据分析…

ML-Agents 概述(二)

注&#xff1a;本文章为官方文档翻译&#xff0c;如有侵权行为请联系作者删除 ML-Agents Overview - Unity ML-Agents Toolkit–原文链接 ML-Agents 概述&#xff08;一&#xff09; ML-Agents 概述&#xff08;二&#xff09; 训练方法&#xff1a;特定环境 除了上一节介绍的…

nlp新词发现——浅析 TF·IDF

传统nlp任务处理文本及其依赖已有的词表&#xff0c;只有在词表里出现的词才能被识别并加以处理。但这也带来了一些问题&#xff1a; 假设没有词表&#xff0c;如何从文本中发现新词&#xff1f; 随着时间推移&#xff0c;新词会不断出现&#xff0c;固有词表会过时&#xff0…

OpenAI 普及 ChatGPT,开通热线电话,近屿智能深耕AI培训

12月19日&#xff0c;在OpenAI直播活动的第10天&#xff0c;宣布允许用户通过电话或WhatsApp与ChatGPT进行交互。并在美国推出 ChatGPT 热线电话&#xff0c;用户拨打后可与 ChatGPT 进行语音对话。 这项服务的一个亮点在于它兼容各种类型的通信设备——不论是现代智能手机如iP…

vue中proxy代理配置(测试二)

接口地址&#xff1a;https://ss.dd.dd.d.cn:9006/thirdist/portalApi/biz-api/eemp/supervision/v1//getPeiCountData?batch2&cent0 1、配置一&#xff08;代理没起作用&#xff09; &#xff08;1&#xff09;设置baseURL为https://ss.dd.dd.d.cn:9006 &#xff08;2&am…