# MD5 计算时间的实践
# 引言
最近在做一个项目,里面用到了 MD5 算法来生成文件名,并且可以对相同文件进行合并存储,不禁让我想到了一个问题 ——MD5 的计算很耗时吗还是 MD5 是一个轻量级的算法,在网上翻阅了一些资料,得到了结果,确实 MD5 的计算是很轻量的。
但是,他到底需要多长时间呢?我直接开始实践一下。
# 配置
机器采用:AMD Ryzen 7 5800H 处理器 + 32GB 内存 频率为 3200 使用约 50%
# 实践
先对小文件进行测试,现选用文件大小为 2.83 MB 的图片,在 anaconda 环境下,对比不同 chunk_size 下的计算时间。
import hashlib | |
import time | |
def file_to_md5(file_path, chunk_size=8192): | |
md5 = hashlib.md5() | |
with open(file_path, 'rb') as f: | |
while chunk := f.read(chunk_size): | |
md5.update(chunk) | |
return md5.hexdigest() | |
t = time.perf_counter() | |
file_to_md5('D:\\ZzMu\\Aping\\bz\\zb.png') | |
print(f'{time.perf_counter() - t:.8f}s') |
chunk_size | 时间 |
---|---|
1024 | 0.00551090s |
2048 | 0.00501470s |
4096 | 0.00491630s |
8192 | 0.00470980s |
16384 | 0.00436570s |
随着 chunk_size 的增大,计算时间不断缩短,不过确实微乎其微,0.00 几的时间无法感知,那么大一点的文件能放大影响吗?
下面对文件大小为 957 MB 的 4 分 44 秒的 5k 视频进行计算。
chunk_size | 时间 |
---|---|
1024 | 1.68504020s |
2048 | 1.61570920s |
4096 | 1.57428070s |
8192 | 1.55151410s |
16384 | 1.35576710s |
32768 | 1.28117580s |
大文件对 chunk_size 的感知更为明显,变现为 0. 几的波动。
下面试试更大的文件,文件大小为 9.58 GB 的 Linux 安装文件 CentOS-7-x86_64-Everything-2207-02.iso 进行计算。
chunk_size | 时间 |
---|---|
1024 | 17.36447650s |
2048 | 17.03117640s |
4096 | 16.44976660s |
8192 | 16.01030290s |
16384 | 14.48009140s |
32768 | 13.57228980s |
得出结论,越大的文件对 chunk_size 越敏感。到此为止,我仍然对这个执行时间有疑问,不同的变成语言对计算时间有什么影响呢,以上是使用 Python 语言完成的测试,接下来试试 Java 的语言。
原生:
import java.io.FileInputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.security.MessageDigest; | |
import java.security.NoSuchAlgorithmException; | |
public class Main { | |
private static final int BUFFER_SIZE = 32768; | |
public static String fileToMD5(String filePath) throws IOException, NoSuchAlgorithmException { | |
try (InputStream is = new FileInputStream(filePath)) { | |
MessageDigest md5Digest = MessageDigest.getInstance("MD5"); | |
byte[] buffer = new byte[BUFFER_SIZE]; | |
int bytesRead; | |
while ((bytesRead = is.read(buffer)) != -1) { | |
md5Digest.update(buffer, 0, bytesRead); | |
} | |
return bytesToHex(md5Digest.digest()); | |
} | |
} | |
private static String bytesToHex(byte[] bytes) { | |
StringBuilder hexString = new StringBuilder(); | |
for (byte b : bytes) { | |
String hex = Integer.toHexString(0xff & b); | |
if (hex.length() == 1) hexString.append('0'); | |
hexString.append(hex); | |
} | |
return hexString.toString(); | |
} | |
public static void main(String[] args) throws IOException, NoSuchAlgorithmException { | |
long startTime = System.nanoTime(); | |
fileToMD5("D:\\ZzMu\\Aping\\bz\\zb.png"); | |
long endTime = System.nanoTime(); | |
System.out.printf("Time taken: %.8fs%n", (endTime - startTime) / 1e9); | |
} | |
} |
使用 java 8:
chunk_size | 时间 |
---|---|
8192 | 22.89824750 |
16384 | 21.72088020 |
32768 | 20.98676390 |
没想到啊,java 表现的更为糟糕,是不是和 java 版本有关系呢?
使用 java 17:
chunk_size | 时间 |
---|---|
1024 | 27.40612860 |
8192 | 15.47918140 |
16384 | 14.53450360 |
32768 | 13.85291620 |
确实 java 17 的表现要比 java 8 的表现好。不过还是弱于 Python?难道是我写的代码太烂了?
又引入了 DigestUtils 工具类,DigestUtils 是 apache. commons 包下的的 MD5 计算工具,默认 chunk_size 大小为 1024。
import org.apache.commons.codec.digest.DigestUtils; | |
import org.junit.jupiter.api.Test; | |
import java.io.FileInputStream; | |
import java.io.IOException; | |
public class MD5 { | |
@Test | |
public void test() throws IOException { | |
FileInputStream inputStream = new FileInputStream("E:\\LinuxIso\\CentOS-7-x86_64-Everything-2207-02.iso"); | |
long startTime = System.nanoTime(); | |
DigestUtils.md5Hex(inputStream); | |
long endTime = System.nanoTime(); | |
System.out.printf("Time taken: %.8fs%n", (endTime - startTime) / 1e9); | |
} | |
} |
chunk_size | 时间 |
---|---|
1024 | 27.50305870 |
彼此彼此吧,哈哈哈哈哈哈。
下面又引入了 SecureUtil,该工具是 HuTool 包下一个计算 MD5 的工具,默认 chunk_size 为 1024。
import cn.hutool.crypto.SecureUtil; | |
import org.junit.jupiter.api.Test; | |
import java.io.FileInputStream; | |
import java.io.IOException; | |
public class MD5 { | |
@Test | |
public void test() throws IOException { | |
FileInputStream inputStream = new FileInputStream("E:\\LinuxIso\\CentOS-7-x86_64-Everything-2207-02.iso"); | |
long startTime = System.nanoTime(); | |
SecureUtil.md5(inputStream); | |
long endTime = System.nanoTime(); | |
System.out.printf("Time taken: %.8fs%n", (endTime - startTime) / 1e9); | |
} | |
} |
chunk_size | 时间 |
---|---|
1024 | 15.42640900 |
HuTool 的 SecureUtil 确实更高效,应该是做了一些特殊优化,源码中显示的是 MD5 没有使用 BC 库,完全依赖于 jdk 库。