当目录中文件数量很多时,通过增加并发数可以提高目录的拷贝速度。Linux 下的 cp 命令是没有并发选项的,通过多次并行执行 cp 命令是可以提高并发数的。但切割任务规模成了难点,特别是目录长短不一时。
我换了一个思路来处理这个问题,将任务细化到文件。目录的拷贝最终还是一堆文件的拷贝,所以可以将文件列表按并发数拆分,然后多线程执行文件拷贝任务。
需求说明:
将目录 d:\x 内的内容拷贝至目标目录 d:\y,要求目标目录不需要提前创建。
如果目标文件已存在并且与源目录文件大小一致则跳过拷贝。
通过增加并发数加快拷贝速度。
程序执行效果如下:

代码如下:
#!python3from path import Pathimport threadingimport osimport timeimport sys# 源地址src_dir = r'd:\x'# 目的地址dst_dir = r'd:\y'# 并发量max_p = 30class Timer():def __init__(self):self.begin_time = time.time()self.end_time = self.begin_timeself.cost = self.end_time - self.begin_timedef count(self):self.cost = self.end_time - self.begin_timeif self.cost > 1:s = "程序耗时:%.3f秒" % self.costelse:if self.cost > 0.001:s = "程序耗时:%d毫秒" % int(self.cost * 1000)else:s = "程序耗时:%d微秒" % int(self.cost * 1000000)print(s)def begin(self):current = time.strftime('%Y-%m-%d %H:%M:%S')print('计时开始:%s' % current)self.begin_time = time.time()self.end_time = self.begin_timedef end(self):self.end_time = time.time()current = time.strftime('%Y-%m-%d %H:%M:%S')print('计时结束:%s' % current)def __str__(self):c = self.end_time - self.begin_timeret = '%.3f' % creturn retdef check_dir(path):d = Path(path)if not d.exists():d.mkdir_p()def get_filesize(path):d = Path(path)if d.exists():return d.sizeelse:return -1def file_copy(copylist):'''拷贝列表中的文件copylist 是 Path 对象组成的列表'''global src_dirglobal dst_dirfor i in copylist:src_fullpath = i.dirname()dst_fullpath = src_fullpath.replace(src_dir, dst_dir)dst_abspath = os.path.join(dst_fullpath, i.name)check_dir(dst_fullpath)if i.size == get_filesize(dst_abspath):#print("Skip: cp " + i.abspath() + " -> \t" + dst_fullpath)passelse:dst = i.copy(dst_fullpath)#print("cp " + i.abspath() + " -> \t" + dst_fullpath)class thread_filecopy(threading.Thread):def __init__(self, copylist):threading.Thread.__init__(self)self.copylist = copylistdef run(self):file_copy(self.copylist)print(self)if __name__ == '__main__':if src_dir == dst_dir:print('Error: 源目录与目标目录重合!')sys.exit()mytimer = Timer()mytimer.begin()# 待拷贝文件列表,元素为Path类型L = []d = Path(src_dir)for i in d.walkfiles():L.append(i)# 文件数量num = len(L)# 向下取整slice_num = round(num max_p)# 分片后的列表p_list = []# 按并分量拆分列表for i in range(max_p):t = L[i*slice_num : (i+1)*slice_num]p_list.append(t)if num > slice_num * max_p:t = L[max_p * slice_num :]p_list.append(t)# 线程列表tasklist = []for i in p_list:tasklist.append(thread_filecopy(i))for i in tasklist:i.start()while True:n = threading.active_count()if n < max_p:breakfor i in tasklist:i.join()print ("退出主线程")mytimer.end()mytimer.count()
在进行真实文件拷贝时可以将 file_copy 函数中的 copy 动作注释掉,看下文件列表遍历的速度。通过调整并发数测试空拷贝的运行速度,并不是并发数越高速度越快。文件拷贝用到了 path 模块,不同 path 版本可能 API 会不一样,我所使用的 path 版本为:
C:\> pip install pathC:\> pip list | findstr pathpath 16.6.0
path 模块对 os.path 进行了包装,使用起来更方便。代码中用到的 path API 有:
# path 库的 API 文档https://path.readthedocs.io/en/latest/api.html
C:\Users\Administrator>pythonPython 3.11.0rc2 (main, Sep 11 2022, 20:22:52) [MSC v.1933 64 bit (AMD64)] on win32Type "help", "copyright", "credits" or "license" for more information.>>> from path import Path>>> f = r'd:\x\免责声明.txt'>>> d = Path(f)>>> d.exists() # 判断文件或目录是否存在True>>> d.dirname()Path('d:\\x')>>> print(d.dirname()) # 文件的目录名d:\x>>> print(d.name) # 文件名,不含目录免责声明.txt>>> print(d.abspath()) # 绝对路径d:\x\免责声明.txt>>> print(d.size) # 文件大小(单位为字节)1294>>> folder = Path(r'd:\y')>>> folder.mkdir_p() # 类似 mkdir -pPath('d:\\y')>>> type(folder.walkfiles()) # 只返回文件,生成器一次生成一个Path对象<class 'generator'>
全文完。
如果转发本文,文末务必注明:“转自微信公众号:生有可恋”。
文章转载自生有可恋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




