情景
MD5是一种常见的方法来进行文件去重,因为它可以为每个文件生成一个唯一的哈希值。
但是,使用MD5来对大量的大文件进行去重可能会非常耗时。这是因为MD5需要读取整个文件内容来计算哈希值。如果文件非常大或者文件数量非常多,这个过程可能会非常慢。
我这里想到的方法是结合文件大小(一个文件多少KB)和文件的名称相似度来去重文件,这样的性能消耗和时间消耗应该会少很多。
获取文件大小和文件名称通常比计算文件的MD5哈希值要快得多。这是因为获取文件大小和名称只需要读取文件系统的元数据,而不需要读取整个文件的内容。这种操作通常可以在常数时间内完成,即它的时间复杂度是O(1)。
相比之下,计算文件的MD5哈希值需要读取整个文件的内容,所以它的时间复杂度是O(n),其中n是文件的大小。对于大文件,这可能需要花费很长时间。
以上是我使用这种方法的依据。
1. 需要使用的库
import os
import difflib
2. 针对某个目录,得到所有的文件名以及文件大小,形成一个元组
元组的第一个元素是文件名,元组的第二个元素是文件大小
def list_files(directory):
# 使用os.listdir遍历指定目录下的所有文件,并获取每个文件的大小
files = [(filename, os.path.getsize(os.path.join(directory, filename))) for filename in os.listdir(directory)]
# 列表生成式
# 列表推导式
# 首先是遍历某个路径下的顶级目录下的所有文件和文件夹,并且获得其名称
# 然后根据每个文件的路径得到文件夹的名称和文件夹的大小,生产一个元组
# 元组的第一个元素是文件名,第二个元素是文件的大小
return files
3. 比较两个文件之间的文件名称差异
这里返回的是相似的百分比,(最终的结果是0 - 1 之间)
值越大,说明越相似
def similar(a, b):
return difflib.SequenceMatcher(None, a, b).ratio()
# difflib 是 Python 的一个内置库,它的名字是 "difference library" 的缩写,
# 意思是 "差异库"。这个库提供了一些类和函数,
# 用于比较序列的差异,包括字符串、列表等。
# 它叫做序列匹配器
# None这个位置的参数是isjunk, 是一个可选参数,
# 如果为None,说明比较的过程中,任何细节都不能忽略
# 如果想输入参数,这个参数需要是一个函数def isjunk(x):return x == ' '
# 意思是,在比较的过程中,无视空格符,空格的差异不计入差异,
4. 根据文件大小来分组文件,相同文件夹大小的文件处于同一个群组(列表)
这个数据的结构是列表里面嵌套列表。
外层列表表示的是,这个列表里面有多个不同的群组。
内层列表表示的是,这个列表里面都是相同文件大小的文件。
def group_files(filenames):
groups = []
for file in files:
# 首先是遍历这个列表
# 这个列表的元素是元组
# 元组的第一个元素是文件名,第二个元素是文件大小
filename, size = file
# 解析(解包)这个元组,分别得到文件名称以及文件大小
added = False
# 初始化added标识符
for group in groups:
# 如果groups中有元素,那么就开始遍历
_, group_size = group[0]
# groups列表中的每一个元素是一个元组
# 该元组的第一个元素是文件的名称,第二个元素是文件的大小
# 在这里,文件夹的名称并不重要,重要的文件的大小
# 因为这个函数主要是根据文件夹的大小来给文件分组
# 相同大小的文件会被分到一个组中,不同文件大小的文件分别在各自的组别中
if abs(size - group_size) / group_size < 0.01:
group.append(file)
added = True
# 如果某个文件夹的大小等于某一个组,那么他就会被归属于某一个组
break
if not added:
# 如果该文件的大小是独一无二的,就会成立一个新的组
groups.append([file])
# 最终得到不同文件大小的组别
return groups
5. 删除重复的文件
如果一批文件在同一个列表中(同一个群组),那么这一批文件可能是完全相同的文件,只需要保留一份即可。
但是还不能这么快下定论,还需要再次把关,排除掉这种状况:虽然两个文件的大小相同,但是实际内容不同。
第二次把关的是文件的名称,如果这两个文件的大小相同,名称也相似,那么它极大极大极大的概率就是重复文件,只留一个即可。
def delete_duplicates(groups, directory):
# 在前面的步骤,我们把相同大小的文件都归类到一个组中
# 如果这个文件的大小相同,文件名也想通,
# 那么这两个文件极大极大的概率就是重复文件
# 那么我们就可以只保留一个,另外一个删除
for group in groups:
# 获取第一个文件的前缀
first_prefix = os.path.splitext(group[0][0])[0]
# os.path.splitext(group[0][0]):这个函数将输入的路径(在这里是 group[0][0])分割成两部分,
# 返回一个元组。
# 元组的第一个元素是路径的前缀部分(也就是文件名中去掉扩展名后的部分),第二个元素是扩展名。
# os.path.splitext(group[0][0])[0]:这个表达式获取上述元组的第一个元素,也就是文件名的前缀部分。
# first_prefix = os.path.splitext(group[0][0])[0]:这个语句将文件名的前缀部分赋值给 first_prefix 变量。
# 保留第一个文件,删除其他的文件
for filename, _ in group[1:]:
# 取相同大小文件群组第一个以后的基于文件
# 这里重要的是文件名,而不是文件的大小
# (元组的第一个元素是文件名,第二个元素是文件大小)
# 获取当前文件的前缀
current_prefix = os.path.splitext(filename)[0]
# 如果前缀的相似度超过55%,则删除文件
# 其余的文件都与第一个文件相比,如果名称相似度到达55%以上,就是重复文件
if similar(first_prefix, current_prefix) > 0.55:
os.remove(os.path.join(directory, filename))
# 否则,打印文件名
else:
print(filename)
6. 完整步骤:
if __name__ == '__main__':
directory = "./AutomationProfile"
files = list_files(directory)
groups = group_files(files)
delete_duplicates(groups, directory)
groups = group_files(files)
print(f"还剩下{len(groups)}个不重复文件")
以上就是我的文件去重初尝试。