需求:查找一件夹中的文件,包括子目录中的文件,找出重名文件,然后比较大小,如果大小也相同,则删除。

方案:递归列出目录底下所有文件,并将同名文件合并到一起,用dict(java里是map)保存,键用文件名,而值用列表,保存文件名对应的路径。然后去除map值中len为1数项目。接着循环map,判断时间是否相同,相同则删除

下面是我的实现过程:

我打算写个递归函数,遍历目录底下的所有文件。这个函数参数除了要遍历的目录路径,还有一个函数参数,用来处理每次遍历的文件。如果是java里面,我当然会用策略模式:比如定义一个处理文件的接口,传给遍历目录的方法。java8里面的lambda表达式也使策略模式使用起来更加方便灵活。但python可以直接传方法作为参数,这样我们就不用定义接口了(事实上我更喜欢java用接口作参数。)

第一步遍历文件,并按文件名,进行分组操作,代码如下

import os
import os.path


##处理文件
def processFile(path, processFun):
    '''
    用来处理遍历文件
    :param path: 要处理文件路径
    :param processFun: 对某一文件处理的方法
    :return: 
    '''
    fileList = os.walk(path)
    for dirpath, dirnames, filenames in fileList:
        for mFname in filenames:
            processFun(dirpath, mFname)


rFdic = dict();

def process1(dirpath, filename):
    
    
    if (filename not in rFdic):
        rFdic[filename] = [dirpath]
    else:
        rList = rFdic[filename]
        rList.append(dirpath)



processFile("/Users/wzp/tmp/file/", process1)
for k1 in [k for k in rFdic if len(rFdic[k]) ==1]:
    rFdic.pop(k1)

print(rFdic)



processFile方法是我计划用来实逻辑的主体的方法。里面实现了对文件的遍历(我本想用递归的,但python里面有现成的方法)。此外遍历到每个文件时调用processFun方法(不好意,我没有做精心的方法命名)。这个函数是作为参数传进来的。这样可以实现我业务的分离。下面调用processFile且传递了相应的路径,将方法process1参数传了进去。process1主要实现了分组操作,代码很简单。但写完后,我发现了一个问题。那就是分组完成后,我希望对数据再进行一个处理,比如在调用processFile后,我用了一个for方法,将rFdic中的没有重复的数据去除掉。然而我并不想在方法外做,我想最后只用调用processFile便可完成所有操作。怎么办?当然是在方法上增加一个参数,这个参数也传递一个方法,这个方法在遍历文档结束后调用,用来处理分组后的数据。妈的,我突然想到,我的做法好像跟google的MapReduce相似。虽然我还没仔细研究过MapReduce,但MapReduce的思路好像也是类似。好吧,我添加一个参数,同时借用google的map和reduce方法名(虽然我原计划我processFile方法不局限于分组删除之类的,不过我一时取不出好的方法名),

版2 的代码如下:

import os
import os.path



def processFile(path, filemap, filereduce):
    fileList = os.walk(path)
    for dirpath, dirnames, filenames in fileList:
        for mFname in filenames:
            filemap(dirpath, mFname)
    filereduce()


rFdic = dict();


def map1(dirpath, filename):
    #  print(dirpath + filename)
    if (filename not in rFdic):
        rFdic[filename] = [dirpath]
    else:
        rList = rFdic[filename]
        rList.append(dirpath)


def reduce1():
    for k1 in [k for k in rFdic if len(rFdic[k]) == 1]:
        rFdic.pop(k1)

    for k1 in rFdic:
        for mdir in rFdic[k1]:
            mpath = mdir + "/" + k1
            print(mpath + "\t|\t", os.path.getsize(mpath))


def reduce2():
    for k1 in [k for k in rFdic if len(rFdic[k]) == 1]:
        rFdic.pop(k1)

    for k1 in rFdic:
        mfsize = -1
        for mdir in rFdic[k1]:
            mpath = mdir + "/" + k1
            tfsize = os.path.getsize(mpath)
            if (tfsize == mfsize):
                print("del file"+mpath,tfsize)

            mfsize = tfsize;


processFile("/Users/wzp/tmp/file/", map1, reduce2)

print(rFdic)




我们改变了 processFile的参数名,并且增加了一个参数。你可以看到我这里用的方法跟mapreduce是不一样的,mapreduce中用map输出的参数作为reduce输入的参数。我们这里没有这个必要,因为这两个方法可以共享一个变量,当然也可以把两个方法写到一个类中,这样就不用完全局变量了。此外,我们这里有个reduce1方法,和reduce2方法。他们可以实现不同的逻辑,在调用processFile时,想要什么样的算法,可以传相应的reduce进去。你可能注意到redduce1和reduce2有两段重复的代码。其实如果是java中,可以用组合模式。

版本3:

import os
import os.path



def processFile(path, filemap, filereduce):
    fileList = os.walk(path)
    for dirpath, dirnames, filenames in fileList:
        for mFname in filenames:
            filemap(dirpath, mFname)
    filereduce()


rFdic = dict();


def map1(dirpath, filename):
    #  print(dirpath + filename)
    if (filename not in rFdic):
        rFdic[filename] = [dirpath]
    else:
        rList = rFdic[filename]
        rList.append(dirpath)

def reduce0():
    for k1 in [k for k in rFdic if len(rFdic[k]) == 1]:
        rFdic.pop(k1)

def reduce1():
    for k1 in rFdic:
        for mdir in rFdic[k1]:
            mpath = mdir + "/" + k1
            print(mpath + "\t|\t", os.path.getsize(mpath))


def reduce2():


    for k1 in rFdic:
        mfsize = -1
        for mdir in rFdic[k1]:
            mpath = mdir + "/" + k1
            tfsize = os.path.getsize(mpath)
            if (tfsize == mfsize):
                os.remove(mpath)
                print("deleted file :"+mpath)

            mfsize = tfsize;


def reduce3():
    reduce0()
    print("do you want delete these files?")
    reduce1()

    str = input("Enter Y or N ")

    if str != "Y":
       print("file not delete ")
       return
    print("start delte file")
    reduce2()


processFile("/Users/wzp/Downloads/xx", map1, reduce3)




我们没有改变我们processFile方法,只是添加两个reduce方法,在方法reduce3里面,我们调用了其它几个reduce方法。ok完成。尽管功能已完成,但还是不如人意,于是我想到了版本5(你或许会问,版本4呢?4没贴出来,我想直接跳5)。首先我们是文件名与文件大小来判断是否是同一文件。于是我们在map方法中,就能按文件名与文件大小联合在一起分组。

def fileNameAndSizeMap(dirpath, filename):
    mFilePath = dirpath + "/" + filename
    mkey = mFilePath + "|" + str(os.path.getsize(mFilePath));

    if (mkey not in rFdic):
        rFdic[mkey] = [dirpath]
    else:
        rList = rFdic[mkey]
        rList.append(filename)

            




processFile("/Users/wzp/Downloads/xx", fileNameAndSizeMap, lambda:print(rFdic))



其他方法不用改变,我们只需要添加两个方法即可,以上我添加map方法,用文件名与文件大小作为键,值改成了文件全路径,这样可以省去很多麻烦,但我没我写reduce了,引用lambda表达式,直接将rFdic中的值直接打印出来,以后再完善。困了,半睡眠状态写的