php或linux如何更高效率地筛选20万张图片,从里面删掉不要的部分?

young8704
  • 250

公司有一个技术落后的网站项目,目录/uploads及其下面有多个以日期命名的子目录,保存了用户上传的图上约20万张,其中大部分是没用的,也就是用户不需要的。

有用的一小部分以url方式记录在数据库mysql某个表中,此表有5000多条记录,以字段contents记录包含图片url的数据,字段contents字段还是json_encode(array()), array里面有文字,也有图片的url。
`

$array_contents=array(
    'title'=>'*******',
    'msg'=>array(...),
    'img_1'=>'http://domain.com/uploads/aaaa_2011101501.jpg',
    'img_2'=>'http://domain.com/uploads/20111015/aaaa_20111015001.jpg',
    'img_3'=>'http://domain.com/uploads/20111015/aaaa_20111015002.jpg',
    'img_4'=>'http://domain.com/uploads/aaaa_2011101502.jpg',
    ...
);

`

现在老板要求把没用的图片删掉,只保留数据库中存储的一部分。

我目前没有好办法,只想到循环。 我只能想到的思路是把所有有用的图片url提出来作为一个array_valid,所有20万张图片作为array_all,但这样一来,循环的数量太大,隐约感觉会卡死。

麻烦大家提点下,不论是用php还是linux命令,如何更高效率地筛选20万张图片,从里面删掉不要的部分?

回复
阅读 1.9k
7 个回答

1 20万条数据不算什么大数据,20亿都没事
2 将数据库中的所有的图片地址取出来放在一个临时文件里
3 执行linux命令将临时文件里的图片复制到另一个临时目录里
4 rm upload里所有的文件
5 将临时目录命名为upload 完事

你没有必要写一堆代码 ,就几个命令的事情

才 20W 的数据量……能有啥瓶颈?是不是想多了……

不就是个取差集么,Shell 里直接就能做了,用不着那么复杂:

# 你自己先把数据库的路径取出来,开头域名自己 replace 替换成本地路径
# 一行一个,存到 /tmp/src-db.txt 里

# 进到你自己存图片的那个目录里
cd /uploads 

# 列出当前目录下所有文件(含子目录)的完整路径
# 一行一个,存到 /tmp/src-dir.txt
ls -R |awk '{print i$0}' i=`pwd`'/' > /tmp/src-dir.txt

# 取 src-db.txt、src-dir.txt 差集
# 结果存到 /tmp/output.txt
sort /tmp/src-db.txt /tmp/src-dir.txt | uniq -u > /tmp/output.txt

# /tmp/output.txt 里就是本地有、数据库里没有的所有文件路径了,挨个删除吧
# 写 Shell 循环或者 PHP 循环都行,看你自己意思了

做好备份机制,确保你做出什么骚操作,最低限度可以还原。
这个问题本质上是如何快速确认一个元素是否存在于一个集合中。
首先这个集合不是很大,5000多条记录,根据数据特点删除没用的字段(比如你的每个图片都含有http://domain.com/uploads/,那么这实际上是无用信息),然后这个集合在查询过程中是静态的,即不会发生插入/删除数据。
其次,查询问题归根结底利用如下几个特性:集合中元素的序关系,通过排序+二分查找 / 平衡树等在对数时间复杂度内进行查询;利用hash将集合中的元素映射,在常数时间复杂度内进行查询。
关于如何选取hash函数要根据数据特点,最终目标是要让数据尽量均匀分布,不产生过多碰撞。比如你的图片url根据日期分布的情况怎么样,你可以先进行预处理大致判断下。
最后,考虑一些解决此类问题的经典结构:https://www.cnblogs.com/cpsel...
布隆过滤器的准确率会随着集合元素的数目而降低,所以可以考虑先将集合根据日期分成几个独立的块,分别对每个块独立的建立hash表和布隆过滤器,实质上就是首先通过分片的方式做了一次hash。

结合你手头可以用的计算资源,尝试性的只利用hash表进行少量数据测试,估计需要多少时间,这个时间能不能接受,如果可以的话就不用利用布隆过滤等手段了。

我觉得你现在在担心一个问题。就是担心数组存太多元素,内存会撑掉。
1.要删掉的图片的路径 一行一个先查询出来存储到csv里面。
2.然后cli执行php 按行读取csv 一个个删除。同时做好日志防止异常情况。

代码就不讲了。
1.把要保留的图片路径一张张遍历,存入到数据库或者redis;
2.新建一个文件夹uploadss,通过php脚本把一张张图片从原来的uploads复制到新的uploadss目录中。
3.最后这一步就是检测了。同样进行上面第一步,通过file_get_contents()判断新的路径uploads里面是否存在。
4.如果都没问题了,就可以通过linux删除uploads,然后uploadss重命名uploads

首先这个应该是一个后台处理
其次这个处理如果以后还会出现,可以考虑成为一个定期自动执行的处理

具体操作:

  1. 过滤出所有用到的图片全路径(真实的服务器本地存储路径)A集,这个需要根据表数据来提取,方法很多,注意需要替换服务器头部分,即http://domain.com/uploads//PATH-base(即服务器上存储上传文件的路径)
  2. 排序A集
  3. 遍历/PATH-base生成所有图片的全路径集合B集并排序
  4. 因为B肯定是A的超集,所以使用diff --unchanged-line-format= --old-line-format= --new-line-format='%L' A B就可以获取到不需要的文件路径集合C
  5. 再依次删除即可

有用的数据少,没用的数据多的情况下,我觉得你直接新建一个目录,把有用的这些数据循环复制到新目录里面,然后删除原目录就好了吧

宣传栏