php操作文件一般是file、file_get_contents等此类函数。但是如果处理大文件,这些函数受限于性能和内存,可能就不是那么理想了!
对于PHP操作文件,我们尝试以下几种方式
一、file
file 函数是一次性将所有内容读入内存,而 php 为了防止一些写的比较糟糕的程序占用太多的内存而导致系统内存不足,使服务器出现宕机,所以默认情况下限制只能最大使用内存 16M,这是通过 php.ini 里的 memory_limit = 16M 来进行设置,这个值如果设置-1,则内存使用量不受限制.
ini_set('memory_limit','-1');
$file = 'access.log';
$data = file($file);
$line = $data[count($data)-1];
var_dump($line)
这种方式,理论上来说,内存多少就可以操作多大的文件
二、tail
我们知道Linux下有个tail命令,常常用来分析日志信息
比如 tail -n 10 access.log 很轻易的显示日志文件最后几行,可以直接用 php 来调用 tail 命令,执行 php 代码如下.
file = 'access.log';
$file = escapeshellarg($file); // 对命令行参数进行安全转义
$line = tail -n 1 $file;
echo $line;
当前用PHP使用tail是不太方便的,最好直接在Linux环境下用tail命令操作文件,这可能会涉及一些服务器权限问题
三、fseek
fseek() 函数在打开的文件中定位。
该函数把文件指针从当前位置向前或向后移动到新的位置,新位置从文件头开始以字节数度量。
成功则返回 0;否则返回 -1。注意,移动到 EOF 之后的位置不会产生错误。
简单来说: 这种方式是最为普遍的方式,它不需要将文件的内容全部读入内容,而是直接通过指针来操作,所以效率是相当高效的.
3.1基本
int ftell(resource handle) //返回文件指针的当前位置
int fseek(resource hanlde,int offset[,int whence]) //移动文件指针到指定位置
bool rewind(resource handle) //移动文件指针到文件的开头
!!! 使用这些函数时,必须提供一个用fopen()函数打开的、合法的文件指针
3.2解读
-$fp = fopen('data.txt' ,'r')or die("文件打开失败");
-echo ftell($fp)."<br>"; //输出刚打开文件的指针默认位置,指针在文件的开头位置为0 echo
-fread($fp, 10)."<br>"; //读取文件中的前10个字符输出,指针位置发生了变化
-echo ftell($fp)."<br>"; //读取文件的前10个字符之后,指针移动的位置在第10个字节处
-fseek($fp, 100,SEEK_CUR); //又将指针移动100个字节
第三个参数:
//SEEK_SET - 设定位置等于 offset 字节。默认。
//SEEK_CUR - 设定位置为当前位置加上 offset。
//SEEK_END - 设定位置为文件末尾加上 offset (要移动到文件尾之前的位置,offset 必须是一个负值)。
-echo ftell($fp); //文件的位置在110个字节处
-echo fread($fp,10)."<br>"; //读取110到120字节数位置的字符串,读取后指针的位置为120
-fseek($fp,-10,SEEK_END); //又将指针移动到倒数10个字节位置处
-echo fread($fp, 10)."<br>"; //输出文件中最后10个字符
-rewind($fp); //又移动文件指针到文件的开头
-echo ftell($fp); //指针在文件的开头位置,输出0
-fclose($fp);
代码:
function readMaxFile($fp , $start = 0)
{
$tag = "\n";
$i = 0;
$content = '';
while($i < 20)
{
if (feof($fp))
{
return 0;
}
fseek($fp, $start, SEEK_SET);
$res = fread($fp, 1);
$content .= $res;
if (substr($content, -strlen($tag)) == $tag)
{
$i++;
echo $i." ->+++这里我插入到数据库+++"."\n";
}
$start+=1;
if (feof($fp))
{
return 0;
}
}
sleep(3);
echo "从位置".$start."开始读取";
return $start;
}
$fp = fopen("install.log", "r+");
$re = readMaxFile($fp, 0);
for($i=0; $i<100; $i++)
{
if ($re==0)
{
echo 'a函数返回0了循环结束';
break;
}
$re = readMaxFile($fp, $re);
}
fclose($fp);
echo "程序结束";
四、yield关键字
使用 yield 关键字返回,下面是我最近使用的代码:
yield 返回的是生成器对象(不了解的可以先去了解一下 PHP 生成器),并没有立即生成数组,所以目录下文件再多也不会出现巨无霸数组的情况,内存消耗是低到可以忽略不计的几十 kb 级别,时间消耗也几乎只有循环消耗。
protected function globforeach($path, $include_dirs=false) {
if (is_dir($path)) {
$dirs = opendir($path);
if ($dirs) {
while(($file = readdir($dirs)) !== false) {
if($file !== '.' && $file !== '..') {
$excel = $path . '/' .$file;
if (is_file($excel)) {
echo $excel . "开始处理\r\n";
yield $excel;
} elseif (is_dir($excel)) {
$sub = $this->globforeach($excel, $include_dirs);
while ($sub->valid()) {
yield $sub->current();
$sub->next();
}
if ($include_dirs) {
yield $excel;
}
}
}
}
}
closedir($dirs);
} else {
echo "目录不存在";
exit;
}
}
public function action($argv = array()) {
require dirname(__DIR__) . '/../../../../vendor/autoload.php';
$path = "/tmp/songmingshuo";
$glob = $this->globforeach($path);
while ($glob->valid()) {
$filename = $glob->current(); //当前文件
//$data = $this->getData($filename);
//$this->doAdd($data);
$glob->next(); //指向下一个文件
}
}
同时处理文件时,也采用yield关键字处理
<?php
function read_file($path) {
if ($handle = fopen($path, 'r')) {
while (! feof($handle)) {
yield trim(fgets($handle));
}
fclose($handle);
}
}
// 使用
$glob = read_file('/var/www/hello.txt');
while ($glob->valid()) {
// 当前行文本
$line = $glob->current();
// 逐行处理数据
// $line
// 指向下一个,不能少
$glob->next();
}
读取excel表格实例
public static function getExcelData()
{
$excelInfo['path'] = 'D:\phpstudy_pro\WWW\api.321.design\Upload\excel\20201230\a795e09f355d48b018421f0f58c42814.xlsx';
$inputFileType = \PHPExcel_IOFactory::identify($excelInfo['path']);
$objReader = \PHPExcel_IOFactory::createReader($inputFileType);
$worksheetNames = $objReader->listWorksheetNames($excelInfo['path']);
#只读取表格数据,忽略里面的各种格式,否则会内存耗尽
$objReader->setReadDataOnly(TRUE);
$objReader->setLoadSheetsOnly($worksheetNames[11]);#笔者此处加载第11个sheet
$objPHPExcels = $objReader->load($excelInfo['path']);
$maxCol = $objPHPExcels->getSheet(0)->getHighestColumn();#总列数
$maxRow = $objPHPExcels->getSheet(0)->getHighestRow();#总行数
$a = 'A';
for ($i = 1; $i <= $maxRow; $i++) {
yield $objPHPExcels->getSheet(0)->rangeToArray('A' . $i . ':' . $maxCol . $i)[0];#读取一行
$a++;
}
}
参考文章: