老旧话题:PHP 读取超大文件

Posted Web开发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了老旧话题:PHP 读取超大文件相关的知识,希望对你有一定的参考价值。

链接:https://segmentfault.com/a/1190000017205171


作为一名常年深耕curd的phper,关注内存那是不可能的,反正apache或者fpm都帮我们做了,况且运行一次就销毁,根本就不存在什么内存问题。

然而偏偏就有些个不开眼的人把这些个东西当面试题,比如总有刁民用“php读取一个10G的超大文件”当面试题来问你。当然了,作为一个和我一样的普普通通的蠢货,你听到这个问题的第一瞬间是懵逼,第二瞬间是卧槽,第三瞬间是保持结巴状态。

“面试造火箭,入职拧螺丝”。然而,刚进来就拧螺丝的人如果能够对“PHP读取一个10G的超大文件”有所见解的话,“造火箭”也是迟早的事儿。当前为了能够来这里“拧螺丝”,还是得先搞定“读取10G文件”这个问题。

要想读取10G的文件,首先,你得有个10G的文件。

老旧话题:PHP 读取超大文件

老旧话题:PHP 读取超大文件

其实,相对来说也是比较简单的事情,我们随便找一个nginx的日志文件,哪怕只有10KB,假设文件名是test.log,然后呢执行" cat test.log >> test.log ",听我说少年,30秒左右你就该按下ctrl + C了,比如我这里,你们感受一下:

老旧话题:PHP 读取超大文件

202MB,作为实验演示,够意思了。难不成真要造10G的文件?

首先,我们尝试用php的file函数来作一把死,你们感受一下:

 
   
   
 
  1. <?php

  2. $begin = microtime( true );

  3. file( './test.log' );

  4. $end = microtime( true );

  5. echo "cost : ".( $end - $begin ).PHP_EOL;

保存为test.php,然后命令行下执行一把,结果如下图所示:

老旧话题:PHP 读取超大文件

这句英文的大概意思就是“PHP最大只给每个进程分配了128MB内存,然而你特么张口要202MB?”所以,我们修改一下php配置文件... ...

老旧话题:PHP 读取超大文件

千万不要手软,把这个参数改成1024MB,然后再次执行上面的php脚本:

老旧话题:PHP 读取超大文件

然后,我们再试试最爱的filegetcontents()函数,结果如下图:

老旧话题:PHP 读取超大文件

文件已经一次性全部被载入到内存中并将文件的每一行保存到了一个php数组中,我的机器是10G内存+256G固态硬盘,一次性载入这个202MB的文件file函数用了0.67秒钟、filegetcontents函数用了0.25秒钟(看起来filegetcontent要比file靠谱的多),不过,敲重点的我们调整了配置文件才可以读取202MB的文件,如果摆在我们面前的是一个100G的文件呢?或者说,系统提供的php配置最多之给20MB内存而你又无法修改呢?

我们重点是如何在内存有限的机器上读取体积几百倍于内存的文件。下面,我们把memory_limit调整成16M,开启困难模式。

202MB的文件,允许被分配的内存为16MB,所以,总体思路其实也很简单,就是一点儿一点儿地读,只要每次读取的内容小于16MB,那就一定不会有问题,首先我们感受一下一个字符一个字符读,出场嘉宾是fgetc函数:

 
   
   
 
  1. <?php

  2. $begin = microtime( true );

  3. $fp = fopen( './test.log' );

  4. while( false !== ( $ch = fgetc( $fp ) ) ){

  5.  // ⚠️⚠️⚠️ 作为测试代码是否正确,你可以打开注释 ⚠️⚠️⚠️

  6.  // 但是,打开注释后屏显字符会严重拖慢程序速度!也就是说程序运行速度可能远远超出屏幕显示速度

  7.  //echo $char.PHP_EOL;

  8. }

  9. fclose( $fp );

  10. $end = microtime( true );

  11. echo "cost : ".( $end - $begin ).PHP_EOL;

运行结果如下图:

老旧话题:PHP 读取超大文件

虽然只有给了16M内存,但我们还是成功将202M文件全部读出来了,只不过这个运行速度是差了那么点儿意思,不大行。不能一个字母一个字母地读,这次我们一行一行地读:

 
   
   
 
  1. <?php

  2. $begin = microtime( true );

  3. $fp = fopen( './test.log', 'r' );

  4. while( false !== ( $buffer = fgets( $fp, 4096 ) ) ){

  5.  //echo $buffer.PHP_EOL;

  6. }

  7. if( !feof( $fp ) ){

  8.  throw new Exception('... ...');

  9. }

  10. fclose( $fp );

  11. $end = microtime( true );

  12. echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

运行结果如下图:

老旧话题:PHP 读取超大文件

一行一行果然比一个一个字符要快很多,转念一想吧,系统分配给我们的内存上限是16MB,那我们索性一次读取一定量容量数据看看,会不会更快:

 
   
   
 
  1. <?php

  2. $begin = microtime( true );

  3. $fp = fopen( './test.log', 'r' );

  4. while( !feof( $fp ) ){

  5.  // 如果你要使用echo,那么,你会很惨烈...

  6.  fread( $fp, 10240 );

  7. }

  8. fclose( $fp );

  9. $end = microtime( true );

  10. echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

  11. exit;

保存代码,运行一把,在内存有限的情况下,我们还把时间缩短到了0.1秒!

老旧话题:PHP 读取超大文件

然后我们考虑将问题升级一下,依然是上述这个202M的文件,这次我们要求读取倒数后5行的内容,这个问题看起来屌了些许,用原来的fread啥的虽然奏效但总感觉比较愚蠢。所以,现在又得引入全新的函数来解决这个问题:ftell和fseek。其中,ftell用于告知当前文件读取指针所在位置,fseek可以手动设定文件读取指针的位置。我建议大家去手册上重点观摩一下fseek函数:点击这里http://php.net/manual/en/function.fseek.php

 
   
   
 
  1. <?php

  2. $fp = fopen( './test1.log', 'r' );

  3. $line = 5;

  4. $pos = -2;

  5. $ch = '';

  6. $content = '';

  7. while( $line > 0 ){

  8.  while( $ch != " " ){

  9.    fseek( $fp, $pos, SEEK_END );

  10.    $ch = fgetc( $fp );

  11.    $pos--;

  12.  }

  13.  $ch = '';

  14.  $content .= fgets( $fp );

  15.  $line--;

  16. }

  17. echo $content;

  18. exit;

其中test1.log文件的内容如下:

 
   
   
 
  1. aa

  2. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

  3. cccccccccccccccccccccccccccccccc

  4. dddddddddddddddddddddddddddddddd

  5. eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

  6. ffffffffffffffffffffffffffffffff

  7. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

  8. cccccccccccccccccccccccccccccccc

  9. dddddddddddddddddddddddddddddddd

  10. eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

  11. ffffffffffffffffffffffffffffffff

  12. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

  13. cccccccccccccccccccccccccccccccc

  14. dddddddddddddddddddddddddddddddd

  15. eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

  16. ffffffffffffffffffffffffffffffff

  17. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

  18. cccccccccccccccccccccccccccccccc

  19. dddddddddddddddddddddddddddddddd

  20. eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

  21. ffffffffffffffffffffffffffffffff

  22. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

  23. cccccccccccccccccccccccccccccccc

  24. dddddddddddddddddddddddddddddddd

  25. eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

  26. ffffffffffffffffffffffffffffffff

  27. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

  28. cccccccccccccccccccccccccccccccc

  29. dddddddddddddddddddddddddddddddd

  30. eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

  31. ffffffffffffffffffffffffffffffff

  32. 1111111111

  33. 2222222222

保存文件并运行,结果如下图所示:


●编号681,输入编号直达本文

●输入m获取文章目录

推荐↓↓↓
 

数据库开发

更多推荐

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、ios开发、C/C++、.NET、Linux、数据库、运维等。

以上是关于老旧话题:PHP 读取超大文件的主要内容,如果未能解决你的问题,请参考以下文章

php读取超大文件fseek

如何用PHPExcel读取超大excel文件

c++如何实现超大文件读取

请教一个问题,php如何读取mp3文件

超大文件同步容易中断 怎么办?

超大文件如何计算md5?