暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

并发文件拷贝

生有可恋 2023-03-06
1394

当目录中文件数量很多时,通过增加并发数可以提高目录的拷贝速度。Linux 下的 cp 命令是没有并发选项的,通过多次并行执行 cp 命令是可以提高并发数的。但切割任务规模成了难点,特别是目录长短不一时。

我换了一个思路来处理这个问题,将任务细化到文件。目录的拷贝最终还是一堆文件的拷贝,所以可以将文件列表按并发数拆分,然后多线程执行文件拷贝任务。

需求说明:

  1. 将目录 d:\x 内的内容拷贝至目标目录 d:\y,要求目标目录不需要提前创建。

  2. 如果目标文件已存在并且与源目录文件大小一致则跳过拷贝。

  3. 通过增加并发数加快拷贝速度。


程序执行效果如下:

代码如下:

    #!python3


    from path import Path
    import threading
    import os
    import time
    import sys


    # 源地址
    src_dir = r'd:\x'


    # 目的地址
    dst_dir = r'd:\y'


    # 并发量
    max_p = 30




    class Timer():
    def __init__(self):
    self.begin_time = time.time()
    self.end_time = self.begin_time
    self.cost = self.end_time - self.begin_time

    def count(self):
    self.cost = self.end_time - self.begin_time
    if self.cost > 1:
    s = "程序耗时:%.3f秒" % self.cost
    else:
    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_time

    def 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_time
    ret = '%.3f' % c
    return ret




    def 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.size
    else:
    return -1




    def file_copy(copylist):
    '''
    拷贝列表中的文件
    copylist 是 Path 对象组成的列表
    '''
    global src_dir
    global dst_dir
    for 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)
    pass
    else:
    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 = copylist


    def 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:
    break
    for i in tasklist:
    i.join()


    print ("退出主线程")


    mytimer.end()
    mytimer.count()


    在进行真实文件拷贝时可以将 file_copy 函数中的 copy 动作注释掉,看下文件列表遍历的速度。通过调整并发数测试空拷贝的运行速度,并不是并发数越高速度越快。文件拷贝用到了 path 模块,不同 path 版本可能 API 会不一样,我所使用的 path 版本为:

      C:\> pip install path
      C:\> pip list | findstr path
      path 16.6.0

      path 模块对 os.path 进行了包装,使用起来更方便。代码中用到的 path API 有:

        # path 库的 API 文档
        https://path.readthedocs.io/en/latest/api.html
          C:\Users\Administrator>python
          Python 3.11.0rc2 (main, Sep 11 2022, 20:22:52) [MSC v.1933 64 bit (AMD64)] on win32
          Type "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 -p
          Path('d:\\y')
          >>> type(folder.walkfiles()) # 只返回文件,生成器一次生成一个Path对象
          <class 'generator'>

          全文完。

          如果转发本文,文末务必注明:“转自微信公众号:生有可恋”。

          文章转载自生有可恋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论