测试 mail.rar , 一个1.59GB 的大文件,结果如下:
// 分别测试3次,下面是测试结果; 我的电脑是 intel i5 4核, 8G 内存; 不运行此程序且 不做任何操作 的时候, 观察发现: cpu 10%, mem 70%
// md5ByJavaIO , 观察发现,大概的平均是: cpu 35%, mem 71%
// md5:8ba6b147475af653bc4609d4cdf72148 time:74s 74s 74s; 测试N多次都是74s, 非常稳定
// md5ByFileChannel , 观察发现,大概的平均是: cpu 31%, mem 71%
// md5:8ba6b147475af653bc4609d4cdf72148 time:75s 72s 73s
// md5ByFileChannelAndMappedByteBuffer , 观察发现,大概的平均是: cpu 30%, mem 71%
// md5:8ba6b147475af653bc4609d4cdf72148 time:52s 53s 54s
// fastMd5 , 观察发现,大概的平均是: cpu 31%, mem 80%
// md5:8ba6b147475af653bc4609d4cdf72148 time:51s 52s 51s
// 实际计算时间的时候, 需要减去休眠的100 * 10, 即1s,
代码如下:
import com.twmacinta.util.MD5;
import com.twmacinta.util.MD5OutputStream;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
/**
* 默认的密码字符串组合,apache校验下载的文件的正确性用的就是默认的这个组合
*/
protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
protected static MessageDigest messagedigest = null;
static {
try {
messagedigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsaex) {
System.err.println(MD5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
nsaex.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
long begin = System.currentTimeMillis();
File big = new File("D:\office2013破解版.ISO"); // 810M
// big = new File("C:\Users\xd\Desktop\工作重要资料\xxx.zip"); // 2.25G; 但不能超过2G, 否则 at java.base/sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:994)
big = new File("D:\lk\mail.rar");// 1.59G
String md5 = null;
// 获取文件的 md5; 如果是仅仅是获取字符串的md5, 当然不需要这么复杂。。
for (int i = 0; i < 10; i++) {
// md5 = md5ByJavaIO(big); // java io 的方式
// md5 = md5ByFileChannel(big); // java nio 的方式
// md5 = md5ByFileChannelAndMappedByteBuffer(big); // fast md5
md5 = fastMd5(big);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("md5:" + md5 + " time:" + ((end - begin) / 1000) + "s");
// 分别测试3次,下面是测试结果; 我的电脑是 intel i5 4核, 8G 内存; 不运行此程序且 不做任何操作 的时候, 观察发现: cpu 10%, mem 70%
// md5ByJavaIO , 观察发现,大概的平均是: cpu 35%, mem 71%
// md5:8ba6b147475af653bc4609d4cdf72148 time:74s 74s 74s; 测试N多次都是74s, 非常稳定
// md5ByFileChannel , 观察发现,大概的平均是: cpu 31%, mem 71%
// md5:8ba6b147475af653bc4609d4cdf72148 time:75s 72s 73s
// md5ByFileChannelAndMappedByteBuffer , 观察发现,大概的平均是: cpu 30%, mem内存几乎毫无增长; ssd是 读300M/S; 写入220K-2M
// md5:8ba6b147475af653bc4609d4cdf72148 time:52s 53s 54s
// fastMd5 , 观察发现,大概的平均是: cpu 开始有个高峰,后面趋于平稳,后面是30% ; 大概是80%, mem xxx %
// md5:8ba6b147475af653bc4609d4cdf72148 time:51s 52s 51s
// 实际计算时间的时候, 需要减去休眠的100 * 10, 即1s,
}
/**
* 适用于上G大的文件
*
* @param file
* @return
* @throws IOException
*/
public static String md5ByFileChannelAndMappedByteBuffer(File file) throws IOException {
FileInputStream in = new FileInputStream(file);
FileChannel ch = in.getChannel();
MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
messagedigest.update(byteBuffer);
in.close();
return bufferToHex(messagedigest.digest());
}
private static String md5ByFileChannel(File big) {
if (big == null) {
return null;
}
MessageDigest md;
FileInputStream inputStream = null;
try {
int read = 0;
inputStream = new FileInputStream(big);
md = MessageDigest.getInstance("MD5");
FileChannel channel = inputStream.getChannel();
ByteBuffer buff = ByteBuffer.allocate(2048);
while (channel.read(buff) != -1) {
buff.flip();
md.update(buff);
buff.clear();
}
return bufferToHex(md.digest());
} catch (NoSuchAlgorithmException e) {
return null;
} catch (IOException e) {
return null;
} finally {
try {
if (inputStream != null) inputStream.close();
} catch (IOException e) {
}
}
}
// private String generateMD5(SequenceInputStream inputStream){
private static String md5ByJavaIO(File big) {
if (big == null) {
return null;
}
MessageDigest md;
InputStream inputStream = null;
try {
int read = 0;
inputStream = new FileInputStream(big);
byte[] buf = new byte[2048];
md = MessageDigest.getInstance("MD5");
while ((read = inputStream.read(buf)) > 0) {
md.update(buf, 0, read);
}
return bufferToHex(md.digest());
} catch (NoSuchAlgorithmException e) {
return null;
} catch (IOException e) {
return null;
} finally {
try {
if (inputStream != null) inputStream.close();
} catch (IOException e) {
// ...
}
}
}
private static String fastMd5(File big) {
String md5Str = null;
try {
MD5OutputStream out = new MD5OutputStream(new com.twmacinta.io.NullOutputStream());
InputStream in = new BufferedInputStream(new FileInputStream(big));
byte[] buf = new byte[65536];// 65536 是最佳数值, 大于或小于这个数字都会 导致耗时增加, 不知道为什么..
int num_read;
long total_read = 0;
while ((num_read = in.read(buf)) != -1) {
total_read += num_read;
out.write(buf, 0, num_read);
}
md5Str = MD5.asHex(out.hash());
//System.out.println(md5Str + " " + big);
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
return md5Str;
}
public static String getMD5String(String s) {
return getMD5String(s.getBytes());
}
public static String getMD5String(byte[] bytes) {
messagedigest.update(bytes);
return bufferToHex(messagedigest.digest());
}
private static String bufferToHex(byte bytes[]) {
return bufferToHex(bytes, 0, bytes.length);
}
private static String bufferToHex(byte bytes[], int m, int n) {
StringBuffer stringbuffer = new StringBuffer(2 * n);
int k = m + n;
for (int l = m; l < k; l++) {
appendHexPair(bytes[l], stringbuffer);
}
return stringbuffer.toString();
}
private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
char c0 = hexDigits[(bt & 0xf0) >> 4];
char c1 = hexDigits[bt & 0xf];
stringbuffer.append(c0);
stringbuffer.append(c1);
}
public static boolean checkPassword(String password, String md5PwdStr) {
String s = getMD5String(password);
return s.equals(md5PwdStr);
}
}
可见,大文件的时候, 最好还是不要java IO到内存, 可能映射过程就需要比较久时间。NIO MPP是很好的选择。
另外, 我们是否可以继续提高 大文件md5 的计算速度呢? 据说 多线程分块校验。。
我感觉 行得通,MD5算法本身是分块的,其他很多类似的算法比如SHA-1也是的,所以可以支持流式计算,读一块算一块,最后再一次性生成完整hash,完全没有内存爆炸的可能。 除非 算法本身不支持这样做。
我们之前也使用过这样的做法, 就是取每100m的前1m作md5.然后整体再md5一次。但是我感觉不太安全, 也就是说不准确。虽然呢时间少花了很多,但恐怕是容易发生碰撞
最后,我这边使用了一个 fastMD5的java 库, 发现确实可以稍微提升一点速度。 fastMD5 主要作用,大概是 不要NIO, 直接普通FIleInputStream 即可, 但是呢, 使用FIleInputStream 文件最大是2G 。
确实, 现在前端也可以很快完成md5 计算!
补充前端计算的方法,webuploader 支持分片计算md5,设置的分片不要太大就好了。