您所在的位置:首页 / 知识分享

PHP如何读取一个大文件txt

2018.12.06

2952

文件已经一次性全部被载入到内存中并将文件的每一行保存到了一个php数组中,一次性载入这个202MB的文件file函数用了0.67秒钟、file_get_contents函数用了0.25秒钟(看起来file_get_content要比file靠谱的多


普通方式读取:
 true );
file( './test.log' );
$end = microtime( true ); echo "cost : ".( $end - $begin ).PHP_EOL;
结果如下图所示:
 
这句英文的大概意思就是“PHP最大只给每个进程分配了128MB内存,然而你特么张口要202MB?”所以,我们修改一下php配置文件... 
把memory_limit这个参数改成1024MB,然后再次执行上面的php脚本: 
 
我们再使用最爱的file_get_contents()函数,结果如下图: 



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

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

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

$fp = fopen( './test.log' );
while( false !== ( $ch = fgetc( $fp ) ) ){

  //echo $char.PHP_EOL;
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).PHP_EOL;

运行结果如下图:



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

$fp = fopen( './test.log', 'r' );
while( false !== ( $buffer = fgets( $fp, 4096 ) ) ){
  //echo $buffer.PHP_EOL;
}
if( !feof( $fp ) ){
  throw new Exception('... ...');
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

运行结果如下图:


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

$fp = fopen( './test.log', 'r' );
while( !feof( $fp ) ){
  // 如果你要使用echo,那么,你会很惨烈...
  fread( $fp, 10240 );
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;
exit;


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


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



<?php
$fp = fopen( './test1.log', 'r' );
$line = 5;
$pos = -2;
$ch = '';
$content = '';
while( $line > 0 ){
  while( $ch != "\n" ){
    fseek( $fp, $pos, SEEK_END );
    $ch = fgetc( $fp );
    $pos--;
  }
  $ch = '';
  $content .= fgets( $fp );
  $line--;
}
echo $content;
exit;