Java 如何校驗兩個文件內(nèi)容是相同的?
今天做文件上傳功能,需求要求文件內(nèi)容相同的不能重復(fù)上傳。感覺這個需求挺簡單的就交給了一位剛?cè)胄械男峦瑢W(xué)。等合并代碼的時候發(fā)現(xiàn)這位同學(xué)居然用文件名稱相同和文件大小相同作為兩個文件相同的依據(jù)。這種條件判斷靠譜嗎?
從概率上來說遇到兩個文件名稱和大小都一樣的概率確實太小了。這種判斷放在生產(chǎn)環(huán)境中也可以穩(wěn)定的跑上一陣子,不過即使再低的可能性也是有可能的,如果能做到100%就好了。
文件摘要校驗
我相信同學(xué)們都下載過一些好心人開發(fā)的小工具,有些小工具會附帶一個校驗器讓你校驗附帶提供的checksum值,防止有人惡意篡改小工具,保證小工具可以放心使用。
文件Hash校驗
如果兩個文件的內(nèi)容相同,那么它們的摘要應(yīng)該是相同的。這個原理能不能幫助我們鑒定兩個文件是否相同呢?
Java實現(xiàn)文件摘要
帶著這個疑問,我寫了一個文件摘要提取工具類:
- /**
- * 提取文件 checksum
- *
- * @param path 文件全路徑
- * @param algorithm 算法名 例如 MD5、SHA-1、SHA-256等
- * @return checksum
- * @throws NoSuchAlgorithmException the no such algorithm exception
- * @throws IOException the io exception
- */
- public static String extractChecksum(String path, String algorithm) throws NoSuchAlgorithmException, IOException {
- // 根據(jù)算法名稱初始化摘要算法
- MessageDigest digest = MessageDigest.getInstance(algorithm);
- // 讀取文件的所有比特
- byte[] fileBytes = Files.readAllBytes(Paths.get(path));
- // 摘要更新
- digest.update(fileBytes);
- //完成哈希摘要計算并返回特征值
- byte[] digested = digest.digest();
- // 進行十六進制的輸出
- return HexUtils.toHexString(digested);
- }
接下來做幾組對照試驗來證明猜想。
內(nèi)容不變
首先要證明一個文件在內(nèi)容不變的情況下摘要是否有變化,多次執(zhí)行下面的代碼,斷言始終都是true。
- String path = "C:\\Users\\s1\\IdeaProjects\\demo\\src\\main\\resources\\application.yml";
- String checksum = extractChecksum(path, "SHA-1");
- String hash = "6bf4d6c101b4a7821226d3ec1f8d778a531bf265";
- Assertions.assertEquals(hash,checksum);
而且我把文件名改成application-dev.yml,甚至application-dev.txt摘要都是相同的。我又把yml文件的內(nèi)容作了改動,斷言就false了。這證明了單個文件的情況下,內(nèi)容不變,hash是不變的。
文件復(fù)制
我把yml文件復(fù)制了一份,改了文件名稱和類型,不改變內(nèi)容并存到了另一個目錄中,來測試一下它們的摘要是否有變化。
- String path1 = "C:\\Users\\s1\\IdeaProjects\\demo\\src\\main\\resources\\application.yml";
- String path2 = "C:\\Users\\s1\\IdeaProjects\\demo\\src\\main\\resources\\templates\\application-dev.txt";
- String checksum1 = extractChecksum(path1, "SHA-1");
- String checksum2 = extractChecksum(path2, "SHA-1");
- String hash = "6bf4d6c101b4a7821226d3ec1f8d778a531bf265";
- Assertions.assertEquals(hash,checksum1);
- Assertions.assertEquals(hash,checksum2);
結(jié)果斷言通過,不過改變了其中一個文件的內(nèi)容后斷言就不通過了。
新建空文件
這里的新建空文件指的是沒有進行任何操作的新建的空文件。
新建的空文件會根據(jù)特定的算法返回一個固定值,比如SHA-1算法下的空文件值是:
- da39a3ee5e6b4b0d3255bfef95601890afd80709
結(jié)論
通過實驗證明了:
在相同算法下,任何新建空文件的摘要值都是固定的。
任何兩個內(nèi)容相同的文件的摘要值都是相同的,和路徑、文件名、文件類型無關(guān)。
文件的摘要值會隨著文件內(nèi)容的改變而改變。
文件摘要運用
根據(jù)上面的結(jié)論,文件摘要是可以防止同樣內(nèi)容的文件重復(fù)提交的, 存儲的時候不但要存儲文件的路徑,還要存儲文件的摘要值,可能需要注意新建空文件的的固定摘要問題。另外在Java12中提供了新的API來處理文件內(nèi)容重復(fù)問題,有興趣的可以研究一下。文件摘要除了防篡改和去重之外,你知道還有其它什么用途嗎?歡迎同學(xué)們留言討論。
本文轉(zhuǎn)載自微信公眾號「碼農(nóng)小胖哥」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼農(nóng)小胖哥公眾號。