计算文件md5值时有没有办法避免磁盘占用率太高

记得以前用机械硬盘时,windows动不动就磁盘占用率100%,用了各种办法都没法解决。一直以为这是微软在逼迫用户升级固态硬盘。最后升级固态硬盘解决。直到最近又遇到这个问题。

当文件比较大时,2G以上mp4文件,用php的md5_file()函数计算文件的md5值,cpu 内存都正常,磁盘占用却率升到70%以上。

感觉php解决不了了,就换python,google在这里 https://www.cnblogs.com/xiaodekaixin/p/11203857.html 找到一个python计算文件md5值的封装,但一执行也是一样的问题,遇到大文件磁盘占用率马上升高。

于是来论坛问问,有没有人也遇到过这样的问题。

试过node也是这样,我是在命令行(emacs shell-mode)运行的php,python,换成apache下通过http请求的方式运行php,发现服务器上没有这个问题

md5 sha256 sha1这种计算checksum的,本来就需要文件的所有byte一起参与运算啊,不然怎么保证结果的唯一性

因为算 hash 肯定要读整个文件啊⋯⋯

原则上你可以自己写个程序,算一会儿 sleep 一会儿,边睡边算⋯⋯

判断下文件大小,太大的文件不算md5了,准备结合文件大小,最后修改时间,创建时间等因素来做文件的唯一标识(找出文件有多少个副本),这样有一定概率会出现唯一标识重复的情况,但也没什么好办法,遇到了在考虑解决

我觉得思路都被带到如何提高MD5计算效率上了,不如说说计算文件的MD5的意图是什么?也许不需要计算MD5也可以解决。

是想做一个文件管理库,文件多了以后管理不能只用文件名,需要对文件作唯一标识,比如说百度网盘等各种网盘工具都会有这么一个功能,秒传,几个G的文件很快就传上去了,但其实就没传,原理据说就是计算文件的md5值,然后检查系统里已经有这个文件了,就不用在上传了,给用户显示你的文件已经秒传成功

另外git在提交文件的时候似乎也有类似的操作,但用的不是md5,而是sha1,打开.git/objects/ 目录里面就是sha1算法生成的文件

不过现在百度网盘好像没秒传这功能了,普通用户没有,要充值开会员才有

那我觉得你还是可以用 MD5,但不需要整个文件的 MD5。比如说先计算前4K个字节的 MD5,如果有重复,就计算前 8K个字节的,以此类推。

一般除了固定格式的文件头,或者类似源代码中的样板代码那样的东西的话,在文件的前面部分就会显示出与其它文件的差异了。

文件大小 + 抽样(头、中、尾或每隔xx字节抽样一次)哈希

可不可以将文件分成100份,md5之后,将所有md5字符串合并后再md5一下?

计算哈希值是必须读取所有数据,这个是无法优化的,可以优化的点是把哈希计算和读取哈希分开处理。

后台进程对每个文件计算哈希值,保存成独立的文件。读取哈希的进程只需要读取保存哈希值的文件就可以,这样确保每个文件的哈希值只需要计算一次。

对于大文件,显然抽样计算 hash 比较合适,除非真的需要全部内容。

感谢 @twlz0ne @Liutos @SuperMMX 写了下面段php代码测试了一下,确实文件大小+抽样计算的方式比较合适

function file_md5_16k($path){
  $size=filesize($path);#文件大小
  if($size>16384){//文件大于16kb
    $str=$size;
    $str.=file_get_contents($path,null,null,0,4096);#文件头部4kb
    $str.=file_get_contents($path,null,null,(($size/2)-2048),4096);#文件中部4kb
    $str.=file_get_contents($path,null,null,($size-4096),4096);#文件尾部4kb
    return md5($str);
  }else{
    return md5_file($path);
  }
}

这样的算法风险比较大,16k的文件太小了,现在大一点的源程序都有可能超过16k,如果在中间修改了一些内容很容易就中招了。

另外,把内容全部读入内存进行计算不是很好。

这个算法适用的范围与文件的类型有关。使用时要注意。

2 个赞

感谢大家的回复,已将本贴内容整理成了一篇文章,发到了github和头条

github web/big-file-hash.md at main · wsug/web · GitHub

头条 https://www.toutiao.com/i6902615064763761160/

2 个赞

磁盘占用率应该不用担心吧,OS 应该会自动调整?自己用 sleep 手动限流 throttle?不过最好先找一找现成的库。

hash 可以不用一次性读到内存,一点一点读取,也能计算完整的 hash。下面的 nodejs 的例子:

const filename = process.argv[2];
const crypto = require("crypto");
const fs = require("fs");

const hash = crypto.createHash("md5");

const input = fs.createReadStream(filename);
let size = 0;
input.on("readable", () => {
  // data 默认 64 KB,可由 highWaterMark 控制
  const data = input.read();

  if (data) {
    // 进度条
    size += data.length;
    console.log(`${size / 1024 / 1024} MiB`);

    hash.update(data);
  } else {
    console.log(`${hash.digest("hex")} ${filename}`);
  }
});
1 个赞

还要考虑时间成本吧。