Python實現刪除目錄下相同文件
不要整天往腦袋里塞算法,要適時把算法拿出來,應用到實際開發中!
這兩天閑來無事在百度上淘了點圖片,不多,也就幾萬張吧,其中有不少美女圖片奧!哈哈!這里暫且不說圖片是怎么獲得的,咱聊聊得到圖片以后發生的事。
遇到的***個問題就是有些圖片沒有后綴名。在windows下,沒有后綴名的文件是不能正確被識別的,沒有預覽,打開時還要選擇打開方式,費勁!這個問題比較容易解決,給每個圖片加上后綴名就是了。沒有后綴名的圖片也不多,不到1000張吧,一張一張地改很麻煩,還好我是學計算機的,上午寫了個程序批量修改http://www.cnblogs.com/ma6174/archive/2012/05/04/2482378.html。這個問題就算解決了。接下來又遇到了一個新問題:圖片多了,難免出現重復的,有些圖片完全一樣,沒有必要都留著,我就想把所有的重復圖片都刪除。
讓我們來分析一下這個問題:首先,文件個數非常多,手工查找是不現實的,再說,單憑我們肉眼,在幾千張圖片里面找到完全相同的難度也是很大的。如果不是圖片而是其他文檔,在不能預覽的情況下要正確區分是很困難的。所以要用程序實現。那么用程序怎么實現呢?根據什么判斷兩個文件完全相同呢?首先,根據文件名判斷是靠不住的,因為文件名可以被隨意更改,但文件內容不變。再說在同一個文件夾下面,也不可能出現兩個完全相同的文件名,操作系統不允許的。還有一種方法就是根據文件大小來判斷,這不失為一種好辦法,但是,文件大小相同的圖片可能不一樣。再說圖片一般都比較小,超過3M的基本沒有,大部分不夠1M,如果文件夾下面文件特別多,出現大小相同的的文件可能性是相當大的。所以單憑文件大小來比較不靠譜。還有一種方法是讀取每張圖片的內容,然后比較這個圖片的內容和其他圖片是否完全相同,如果內容相同那么這兩張圖片肯定是完全相同的。這種方法看起來是比較***的,讓我們來分析一下他的時空效率:首先每張圖片的內容都要和其他圖片進行比較,這就是一個二重循環,讀取的效率低,比較的效率更低,所有的都比較下來是非常費時的!內存方面,如果預先把所有圖片讀取到內存可以加快文件的比較效率,但是普通計算機的內存資源有限,如果圖片非常多,好幾個G的話,都讀到內存是不現實的。如果不把所有的文件讀取到內存,那么每比較一次之前就要先讀取文件內容,比較幾次就要讀取幾次,從硬盤讀取數據是比較慢的,這樣做顯然不合適。那么有沒有更好的方法呢?我冥思苦想,絞盡腦汁,***想到了md5。md5是什么?你不知道嗎?額,你火星了,抓緊時間duckduckgo吧!也許你會問,md5不是加密的嗎?和我們的問題有關系嗎?問得好!md5可以把任意長度的字符串進行加密后形成一個32的字符序列,包括數字和字母(大寫或小寫),因為字符串任何微小的變動都會導致md5序列改變,因此md5可以看作一個字符串的‘指紋’或者‘信息摘要’,因為md5字符串總共有3632個,所以兩個不同的字符串得到一個相同的md5概率是很小的,幾乎為0,同樣的道理,我們可以得到每個文件的md5,若干文件的md5相同的話就基本上可以肯定兩個文件是相同的,因為md5相同而文件不同的概率太小了,基本可以忽略,這樣我們就可以這樣做:得到每個文件的md5,通過比較md5是否相同我們就可以確定兩張圖片是否相同。下面是代碼實現,python的。
- # -*- coding: cp936 -*-
- import md5
- import os
- from time import clock as now
- def getmd5(filename):
- file_txt = open(filename,'rb').read()
- m = md5.new(file_txt)
- return m.hexdigest()
- def main():
- path = raw_input("path: ")
- all_md5=[]
- total_file=0
- total_delete=0
- start=now()
- for file in os.listdir(path):
- total_file += 1;
- real_path=os.path.join(path,file)
- if os.path.isfile(real_path) == True:
- filemd5=getmd5(real_path)
- if filemd5 in all_md5:
- total_delete += 1
- print '刪除',file
- else:
- all_md5.append(filemd5)
- end = now()
- time_last = end - start
- print '文件總數:',total_file
- print '刪除個數:',total_delete
- print '耗時:',time_last,'秒'
- if __name__=='__main__':
- main()
上面的程序原理很簡單,就是依次讀取每個文件,計算md5,如果md5在md5列表不存在,就把這個md5加到md5列表里面去,如果存在的話,我們就認為這個md5對應的文件已經出現過,這個圖片就是多余的,然后我們就可以把這個圖片刪除了。下面是程序的運行截圖:
我們可以看到,在這個文件夾下面有8674個文件,有31個是重復的,找到所有重復文件共耗時155.5秒。效率不算高,能不能進行優化呢?我分析了一下,我的程序里面有兩個功能比較耗時間,一個是計算每個文件的md5,這個占了大部分時間,還有就是在列表中查找md5是否存在,也比較費時間的。從這兩方面入手,我們可以進一步優化。
首先我想的是解決查找問題,或許我們可以對列表中的元素先排一下序,然后再去查找,但是列表是變化的,每次都排序的話效率就比較低了。我想的是利用字典進行優化。字典最顯著的特點是一個key對應一個值我們可以把md5作為key,key對應的值就不需要了,在變化的情況下字典的查找效率比序列效率高,因為序列是無序的,而字典是有序的,查找起來當然更快。這樣我們只要判斷md5值是否在所有的key中就可以了。下面是改進后的代碼:
- # -*- coding: cp936 -*-
- import md5
- import os
- from time import clock as now
- def getmd5(filename):
- file_txt = open(filename,'rb').read()
- m = md5.new(file_txt)
- return m.hexdigest()
- def main():
- path = raw_input("path: ")
- all_md5={}
- total_file=0
- total_delete=0
- start=now()
- for file in os.listdir(path):
- total_file += 1;
- real_path=os.path.join(path,file)
- if os.path.isfile(real_path) == True:
- filemd5=getmd5(real_path)
- if filemd5 in all_md5.keys():
- total_delete += 1
- print '刪除',file
- else:
- all_md5[filemd5]=''
- end = now()
- time_last = end - start
- print '文件總數:',total_file
- print '刪除個數:',total_delete
- print '耗時:',time_last,'秒'
- if __name__=='__main__':
- main()
再看看運行截圖
從時間上看,確實比原來快了一點,但是還不理想。下面還要進行優化。還有什么可以優化呢?md5!上面的程序,每個文件都要計算md5,非常費時間,是不是每個文件都需要計算md5呢?能不能想辦法減少md5的計算次數呢?我想到了一種方法:上面分析時我們提到,可以通過比較文件大小的方式來判斷圖片是否完全相同,速度快,但是這種方法是不準確的,md5是準確的,我們能不能把兩者結合一下?答案是肯定的。我們可以認定:如果兩個文件完全相同,那么這兩個文件的大小和md5一定相同,如果兩個文件的大小不同,那么這兩個文件肯定不同!這樣的話,我們只需要先查看文件的大小是否存在在size字典中,如果不存在,就將它加入到size字典中,如果大小存在的話,這說明有至少兩張圖片大小相同,那么我們只要計算文件大小相同的文件的md5,如果md5相同,那么這兩個文件肯定完全一樣,我們可以刪除,如果md5不同,我們把它加到列表里面,避免重復計算md5.具體代碼實現如下:
- # -*- coding: cp936 -*-
- import md5
- import os
- from time import clock as now
- def getmd5(filename):
- file_txt = open(filename,'rb').read()
- m = md5.new(file_txt)
- return m.hexdigest()
- def main():
- path = raw_input("path: ")
- all_md5 = {}
- all_size = {}
- total_file=0
- total_delete=0
- start=now()
- for file in os.listdir(path):
- total_file += 1
- real_path=os.path.join(path,file)
- if os.path.isfile(real_path) == True:
- size = os.stat(real_path).st_size
- name_and_md5=[real_path,'']
- if size in all_size.keys():
- new_md5 = getmd5(real_path)
- if all_size[size][1]=='':
- all_size[size][1]=getmd5(all_size[size][0])
- if new_md5 in all_size[size]:
- print '刪除',file
- total_delete += 1
- else:
- all_size[size].append(new_md5)
- else:
- all_size[size]=name_and_md5
- end = now()
- time_last = end - start
- print '文件總數:',total_file
- print '刪除個數:',total_delete
- print '耗時:',time_last,'秒'
- if __name__=='__main__':
- main()
時間效率怎樣呢?看下圖:
只用了7.28秒!比前兩個效率提高了十幾倍!這個時間還可以接受
算法是個很神奇的東西,不經意間用一下會有意想不到的收獲!上面的代碼還可以進一步優化,比如改進查找算法等,讀者有啥想法可以和我交流一下。換成C語言來實現可能會更快。呵呵,我喜歡python的簡潔!
原文鏈接:http://www.cnblogs.com/ma6174/archive/2012/05/05/2484415.html