今天寫了一個程式,掃描指定目錄,遍歷這個目錄及子目錄下全部檔案,生成每一個檔案的md5值,記錄到一個字典變數,然後在這堆記錄裡面找出重複的檔案。

之前看廖雪峰老師部落格中

摘要演算法簡介

這篇文章,他把md5值講得很透徹,但我理解起來一直有一些朦朦朧朧的疑惑沒有解開,把這個程式測試通過後,我感覺才真正搞懂了md5值,於是有了這篇文章。

首先,介紹一下我寫的這個程式。

1、遞迴遍歷給定目錄下的全部檔案,將檔案路徑存入到一個列表。

2、利用hashlib。md5函式計算每一個有檔案的md5值,將檔案路徑和md5值,儲存到字典變數md5_dict。字典變數的結構是{

“檔案路徑”:“檔案的md5值”

},注意字典裡存放的是檔案的md5值,不是檔案所在路徑的md5值。

3、每生成一個檔案的md5值出來後,先到字典變數dup_dict裡面去匹配,如果md5值匹配成功,說明檔案內容存在重複,那麼將這個出現重複的記錄寫入字典變數dup_dict。

生成檔案路徑的函式dirlist如下。

def

dirlist

path

allfile

shortcutfile

):

filelist

=

os

listdir

path

for

filename

in

filelist

# 排除隱藏檔案

if

not

filename

startswith

‘。’

):

filepath

=

os

path

join

path

filename

if

os

path

isdir

filepath

):

# 如果是folder,呼叫函式自身

dirlist

filepath

allfile

shortcutfile

elif

os

path

isfile

filepath

):

# 如果是file,將檔案路徑存入allfile列表

allfile

append

filepath

else

# 遍歷時如果既不是folder也不是file,那麼很可能就是替身變數,

# 將檔案路徑存入shortcutfile列表

# macOS裡面的替身變數,你可以理解成Windows裡面的快捷方式

shortcutfile

append

filepath

else

filepath

=

os

path

join

path

filename

if

os

path

isdir

filename

):

print

%s

is a hidden folder”

%

filepath

else

print

%s

is a hidden file”

%

filepath

return

allfile

shortcutfile

計算md5值的函式gen_md5如下。

def

gen_md5

file_list

):

for

file_path

in

file_list

#得到檔案屬性

statinfo

=

os

stat

file_path

#檔案大小

sizefile

=

statinfo

st_size

#建立時間

createtime

=

formattime

statinfo

st_ctime

#修改時間

changetime

=

formattime

statinfo

st_mtime

#瀏覽時間

readtime

=

formattime

statinfo

st_atime

# 給小於20M的檔案生成md5值

if

0

<

sizefile

<=

20000000

with

open

file_path

‘rb’

as

fp

md5_value

=

hashlib

md5

fp

read

())

hexdigest

()

if

md5_value

in

md5_dict

values

():

dup_dict

file_path

=

md5_value

else

md5_dict

file_path

=

md5_value

執行結果出來後,我檢查dup_dict字典裡的記錄發現“365c9bfeb7d89244f2ce01c1de44cb85”這個md5值出現了三次,鍵值對記錄如下。

‘/Users/jacksonshawn/PythonCodes/pythonlearning/flask2/venv/lib/python2。7/site-packages/setuptools-28。8。0。dist-info/INSTALLER’: ‘

365c9bfeb7d89244f2ce01c1de44cb85

‘/Users/jacksonshawn/PythonCodes/pythonlearning/flask2/venv/lib/python2。7/site-packages/pip-9。0。1。dist-info/top_level。txt’: ‘

365c9bfeb7d89244f2ce01c1de44cb85

‘/Users/jacksonshawn/PythonCodes/pythonlearning/flask2/venv/lib/python2。7/site-packages/wheel-0。29。0。dist-info/INSTALLER’: ‘

365c9bfeb7d89244f2ce01c1de44cb85

透過檔案路徑檢視每一個檔案的內容,結果如下。每一個檔案的內容都是一個單詞“pip”,檔案大小均為4位元組。可以看出,不同的檔名,只要內容相同,計算出來的md5值是同一個。

真正弄懂md5值是什麼

(圖1)

真正弄懂md5值是什麼

(圖2)

真正弄懂md5值是什麼

(圖3)

原本到這裡就可以愉快地釋出這篇文章了,但是,好奇的我,腦子裡產生一個問題,“在Python3裡面計算這個檔案的md5值,結果會一樣嗎?”

我開啟虛擬機器,在Win7裡面建立一個b。txt檔案和一個b。docx檔案,各自都只有一行內容,一個單詞“pip”,然後使用在Python3。6。4環境下計算著這兩個檔案的md5值。結果出人意料,TXT檔案的md5值是

62ad1c2a46c5298f3e2c95d3babf8d0c

,Word檔案的md5值是

a6e0e738b4ee07c39b6d87e91a1569a7

,都不是預期的結果。

於是,我陷入了沉思,那些朦朦朧朧的疑惑再次縈繞腦海。“同樣的內容,Python2和Python3計算出來的md5值難道不一樣嗎?”

真正弄懂md5值是什麼

(圖4)

網上搜了一大圈,都找不到答案。在微信群裡發帖提問,經一個網友提醒,才發現在Win7裡面新建的TXT和Word檔案,並不是macOS裡面的原檔案,它們不是同一個東西。macOS裡的原檔案大小是4位元組,而Win7裡面的TXT和Word檔案分別是3位元組和11056位元組。這就是md5值不一樣的真正原因,即便它們的內容是相同的。

真正弄懂md5值是什麼

(圖5)

直接將macOS裡原檔案複製到Win7,得到INSTALLER。txt檔案,再計算md5值,這次得到的結果就和預期一致了。

真正弄懂md5值是什麼

(圖6)

緊接著,我在macOS裡面將圖1(你選圖1、圖2、圖3任意一個都可以)裡的檔案,加上一個換行符,計算它的md5值,也得到一個不同於

365c9bfeb7d89244f2ce01c1de44cb85

的結果,很簡單,檔案內容變了。這下我才真正理解廖雪峰Python教程hashlib那一節裡面那段話的含義。md5值的意義主要在於防篡改,哪怕你只改動了一丁點內容,md5值也會發生變化。

舉個例子,你寫了一篇文章,內容是一個字串

‘how to use python hashlib - by Michael’

,並附上這篇文章的摘要是

‘2d73d4f15c0db7f5ecb321b6a65e5d6d’

。如果有人篡改了你的文章,並發表為

‘how to use python hashlib - by Bob’

,你可以一下子指出Bob篡改了你的文章,因為根據

‘how to use python hashlib - by Bob’

計算出的摘要不同於原始文章的摘要。

結論:

其實,同一個檔案或字元,在任何語言、環境裡計算出來的md5值都是相同的,因為全世界的MD5摘要演算法都一樣。在極特殊條件下,md5值會出現碰撞,但它太特殊我們一般遇不到,可以忽略不計。如果對同一個檔案或字元,你計算出來的md5值不同,那麼肯定是這個檔案或字元發生了變化,如同Win7裡那個b。txt檔案,內容也是“pip”,但實際上和macOS裡的INSTALLER並不是同一個檔案。

幾點補充:

第一,

md5值

雜湊值

是兩個概念,千萬別搞混淆。MD5是最常見的一種摘要演算法,它計算出來的結果稱為md5值,它是一個32位長度的16進位制字串。雜湊值是一個整型數字,它主要用於字典查詢時比較字典的key值,對於Python來說,每一個可雜湊的物件都有一個雜湊值,像int、str、unicode、tuple這些物件,都可以計算它們的雜湊值,見圖7;像list、dict、set這些物件,則沒有雜湊值,見圖8。

hash

object

Return the hash value of the object (if it has one)。 Hash values are integers。 They are used to quickly compare dictionary keys during a dictionary lookup。 Numeric values that compare equal have the same hash value (even if they are of different types, as is the case for 1 and 1。0)。

上面是Python裡面對雜湊值下的定義,出處Python hash definition。Java語言裡面有一個hashCode型別,和Python裡面的雜湊值是一個意思,它也是一個整型數字。

真正弄懂md5值是什麼

(圖7)

真正弄懂md5值是什麼

(圖8)

第二,dirlist函數里使用到了遞迴,從列印結果來看,這個遞迴呼叫,實現了類似深度優先的效果。當發現是檔案時才收集檔案路徑,是資料夾則呼叫本身,遍歷下一層,迴圈往復。因此,檔案路徑深的會先被掃描到allfile列表裡面,檔案路徑淺的後被掃描到。

第三,dirlist函數里面判斷檔案型別時又一個else分支,滿足else分支的檔案路徑會被寫入shortcutfile列表。這個shortcutfile列表存入的都是“替身變數”,在macOS裡叫法是“替身變數”,在Windows裡面叫法是快捷方式。我在測試中發現,對於實體檔案的“替身變數”,計算出來的md5值和原檔案的md5值一樣;但對於一些軟連線“替身變數”,沒法計算md5值。這一點,有待日後進一步觀察和研究。

原文連結: