PHP/Shell大文件数据统计并且排序

诸多大互联网公司的面试都会有这么个问题,有个4G的文件,如何用只有1G内存的机器去计算文件中出现次数做多的数字(假设1行是1个数组,例如QQ号码),如果这个文件只有4B或者几十兆,那么最简单的办法就是直接读取这个文件后进行分析统计,但是这个是4G的文件,当然也可能是几十G甚至几百G的文件,这就不是直接读取能解决了的.

同样对于如此大的文件,单纯用PHP做是肯定行不通的,我的思路是不管多大文件,首先要切割为多个应用可以承受的小文件,然后批量或者依次分析统计小文件后再把总的结果汇总后统计出符合要求的最终结果,类似于比较流行的MapReduce模型,其核心思想就是“Map(映射)”和“Reduce(化简)”,加上分布式的文件处理,当然我能理解和使用到的只有Reduce后去处理.

假设有1个10亿行的文件,每行一个6位-10位不等的QQ号码,那么我需要解决的就是计算在这10亿个QQ号码中,重复最多的前10个号码,使用下面的PHP脚本生成这个文件,很可能这个随机数中不会出现重复,但是我们假设这里面会有重复的数字出现,代码如下:

  1. $fp = fopen('qq.txt','w+');
  2. for( $i=0; $i<1000000000; $i++ ){
  3. $str = mt_rand(10000,9999999999)."n";
  4. fwrite($fp,$str);
  5. }
  6. fclose($fp);

生成文件的世界比较长,Linux下直接使用php-client运行PHP文件会比较节省时间,当然也可以使用其他方式生成文件,生成的文件大约11G,然后使用Linux Split切割文件,切割标准为每100万行数据1个文件,代码如下:

split -l 1000000 -a 3 qq.txt qqfile

qq.txt被分割为名字是qqfileaaa到qqfilebml的1000个文件,每个文件11mb大小,这时再使用任何处理方法都会比较简单了,我还是使用PHP进行分析统计,代码如下:

  1. $results = array();
  2. foreach( glob('/tmp/qq/*') as $file ){
  3. $fp = fopen($file,'r');
  4. $arr = array();
  5. while( $qq = fgets($fp) ){
  6. $qq = trim($qq);
  7. isset($arr[$qq]) ? $arr[$qq]++ : $arr[$qq]=1;
  8. }
  9. arsort($arr);
  10. //以下处理方式存在问题
  11. do{
  12. $i=0;
  13. foreach( $arr as $qq=>$times ){
  14. if( $i > 10 ){
  15. isset($results[$qq]) ? $results[$qq]+=$times : $results[$qq]=$times;
  16. $i++;
  17. } else {
  18. break;
  19. }
  20. }
  21. } while(false);
  22. fclose($fp);
  23. }
  24. if( $results ){
  25. arsort($results);
  26. do{
  27. $i=0;
  28. foreach( $results as $qq=>$times ){
  29. if( $i > 10 ){
  30. echo $qq . "t" . $times . "n";
  31. $i++;
  32. } else {
  33. break;
  34. }
  35. }
  36. } while(false);
  37. }

这样每个样本取前10个,最后放到一起分析统计,不排除有个数在每个样本中都排名第11位但是总数绝对在前10的可能性,所以后面统计计算算法还需要改进.

也许有人说使用Linux中的awk和sort命令可以完成排序,但是我试了下如果是小文件还可以实现,但是11G的文件,不管是内存还是时间都无法承受,下面是我改的1个awk+sort的脚本,或许是写法有问题,求牛人指导,代码如下:

  1. awk -F '\@' '{name[$1]++ } END {for (count in name) print name[count],count}' qq.txt |sort -n > 123.txt

互联网几何级增长,未来不管是大文件处理还是可能存在的大数据都存在很大的需求空间.