Java 如何校驗(yàn)兩個(gè)文件內(nèi)容是相同的?
今天做文件上傳功能,需求要求文件內(nèi)容相同的不能重復(fù)上傳。感覺這個(gè)需求挺簡(jiǎn)單的就交給了一位剛?cè)胄械男峦瑢W(xué)。等合并代碼的時(shí)候發(fā)現(xiàn)這位同學(xué)居然用文件名稱相同和文件大小相同作為兩個(gè)文件相同的依據(jù)。這種條件判斷靠譜嗎?
從概率上來(lái)說(shuō)遇到兩個(gè)文件名稱和大小都一樣的概率確實(shí)太小了。這種判斷放在生產(chǎn)環(huán)境中也可以穩(wěn)定的跑上一陣子,不過(guò)即使再低的可能性也是有可能的,如果能做到100%就好了。
文件摘要校驗(yàn)
我相信同學(xué)們都下載過(guò)一些好心人開發(fā)的小工具,有些小工具會(huì)附帶一個(gè)校驗(yàn)器讓你校驗(yàn)附帶提供的checksum值,防止有人惡意篡改小工具,保證小工具可以放心使用。
文件Hash校驗(yàn)
如果兩個(gè)文件的內(nèi)容相同,那么它們的摘要應(yīng)該是相同的。這個(gè)原理能不能幫助我們鑒定兩個(gè)文件是否相同呢?
Java實(shí)現(xiàn)文件摘要
帶著這個(gè)疑問(wèn),我寫了一個(gè)文件摘要提取工具類:
- /**
- * 提取文件 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);
- //完成哈希摘要計(jì)算并返回特征值
- byte[] digested = digest.digest();
- // 進(jìn)行十六進(jìn)制的輸出
- return HexUtils.toHexString(digested);
- }
接下來(lái)做幾組對(duì)照試驗(yàn)來(lái)證明猜想。
內(nèi)容不變
首先要證明一個(gè)文件在內(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)容作了改動(dòng),斷言就false了。這證明了單個(gè)文件的情況下,內(nèi)容不變,hash是不變的。
文件復(fù)制
我把yml文件復(fù)制了一份,改了文件名稱和類型,不改變內(nèi)容并存到了另一個(gè)目錄中,來(lái)測(cè)試一下它們的摘要是否有變化。
- 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é)果斷言通過(guò),不過(guò)改變了其中一個(gè)文件的內(nèi)容后斷言就不通過(guò)了。
新建空文件
這里的新建空文件指的是沒(méi)有進(jìn)行任何操作的新建的空文件。
新建的空文件會(huì)根據(jù)特定的算法返回一個(gè)固定值,比如SHA-1算法下的空文件值是:
- da39a3ee5e6b4b0d3255bfef95601890afd80709
結(jié)論
通過(guò)實(shí)驗(yàn)證明了:
在相同算法下,任何新建空文件的摘要值都是固定的。
任何兩個(gè)內(nèi)容相同的文件的摘要值都是相同的,和路徑、文件名、文件類型無(wú)關(guān)。
文件的摘要值會(huì)隨著文件內(nèi)容的改變而改變。
文件摘要運(yùn)用
根據(jù)上面的結(jié)論,文件摘要是可以防止同樣內(nèi)容的文件重復(fù)提交的, 存儲(chǔ)的時(shí)候不但要存儲(chǔ)文件的路徑,還要存儲(chǔ)文件的摘要值,可能需要注意新建空文件的的固定摘要問(wèn)題。另外在Java12中提供了新的API來(lái)處理文件內(nèi)容重復(fù)問(wèn)題,有興趣的可以研究一下。文件摘要除了防篡改和去重之外,你知道還有其它什么用途嗎?歡迎同學(xué)們留言討論。
本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)小胖哥」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)小胖哥公眾號(hào)。