PHP读取大文件源码示例,经过PHP读取过大、超大型文件的思路及解决方案。swoole
在平常读取文件时,若文件 不是很大,一般使用file_get_contents,将内容一次性载入的变量中,也能够远程加载网页或者远端文件。多线程
若加载超过PHP限制的内存大小,或者超过本机内存大小的文件进程就会报错或者崩掉。spa
为了解决这个问题,咱们采用使用完毕并释放的原则来读取大文件。线程
若是不考虑多线程的状况下,单线程读取大文件采用while fread就能够实现。指针
以下代码code
$handle = fopen("./big.txt", "rb"); while (!feof($handle)) { $contents = fread($handle, 8192); // 业务处理 unset($contents); // 释放掉变量 } fclose($handle);
feof是判断是否到文件尾,若是没有到文件尾,则会一直while循环,并执行读取操做。每次读取8192字节,而后使用事后将其释放掉。进程
每每不少文件并非一行的,有多行内容。须要将每行内容读取出来当作一条数据处理,也可使用fgets。内存
即以下代码:get
$handle = fopen("./big.txt", "rb"); while (!feof($handle)) { $contents = fgets($handle, 1024); // 业务处理 unset($contents); // 释放掉变量 } fclose($handle);
这里的fgets第二个参数,默认为1024字节。即默认读取一行数据,若是一行数据小于1024字节,则完整读取。若是超出1024字节,则只取前1024字节。遇到换行符"\n"或者"\r\n"或者结束符会中止读取。源码
若是遇到变态的文件,不少行都只有1000长度,某一行有8000长度,若是在不清楚的状况下,就很难掌控,若要完整读取就须要指定读取的最大字节,8000才能将每一行完整读取。
还有一种方法就是本身处理换行符。默认读取1024字节,而后放置到内存中,使用事后再将其释放。这种操做很节省内存,可是在逻辑处理上须要本身处理换行符。
如,读取1024字节,没有换行符,则保存数据继续读取。再读取1024字节,判断其中是否有换行符,若是有则处理最开始到换行符中的数据。再将剩下的数据保存,等待下一次读取,直到整个文件读取完毕。
$handle = fopen("./big.txt", "rb"); $contents = ""; while (!feof($handle)) { $contents .= fread($handle, 8192); // 判断读取到的内容是否包含换行符,包含则进入循环体 while(strpos($contents, "\n") !== false){ $eol_pos= strpos($contents, "\n"); $line = substr($contents, 0, $eol_pos); // $line为一行的数据,进行业务处理,并释放 unset($line); $contents = substr($eol_pos, 0);// 将剩余内容放置到变量中以供下次使用 } } fclose($handle);
PHP默认没有多线程,这里能够采用多进程的方式实现,或者swoole的多进程来实现。
例如读取一个8GB文件,分8个线程,每一个线程读取1GB数据内容。或者更多线程进行拆分工做内容。
首先第一步,就是获取整个文件的体积大小,而后计算每一个线程应该负责处理的一部份内容。
function length($filename) { $handle = fopen($filename, "rb"); $currentPos = ftell($handle); fseek($handle, 0, SEEK_END); $length = ftell($handle); fseek($handle, $currentPos); // $length 文件总长度 return $length; } echo length("./big.txt");
这里主要说明下做了哪些内容,首先是设定分配总的线程数。而后根据设置的线程数,计算每一个线程要读取的数据大小,即从哪里开始读,读到哪里结束。
而后一定会出现拆分后,读到不完整行的状况,在这里来解决这种前半行或者后半行的意外状况。
解决逻辑就是,假设线程开始的读取位置在某一行的中间,咱们一个字符向前移动,移动到上个换行符(也多是最开始)便可获取到整行文本内容。
处理掉残行数据以后,使用yield来传递数据给业务处理。
$filename = "./big.txt"; $maxProcess = 8;// 分配8个线程 $length = length($filename); $singleProcessLength = ceil($length / $maxProcess); // 线程负责读取的内容 function processRead($filename, $index, $singleProcessLength) { $fh = fopen($filename, 'r'); $beginPos = $index * $singleProcessLength; //结束位置=线程序列*线程处理数据长度+线程处理数据 - 1 (长度转指针,实际结束指针小于结束长度) $endPos = $index * $singleProcessLength + $singleProcessLength - 1; fseek($fh, $beginPos); echo '线程:' . $index . ',起始位置:' . $beginPos . ',结束位置:' . $endPos . PHP_EOL; //移动到上个\n 以便首次顺利获取整行内容 while (fseek($fh, -1, SEEK_CUR) === 0) { if (fread($fh, 1) == "\n" || ftell($fh) <= 0) { break; } fseek($fh, -1, SEEK_CUR); } echo '线程:' . $index . ',移动完毕!!!!!' . PHP_EOL; //整行读取数据 //结束时位置超过预计结束位置是正常情况,fgets 读取一整行内容 //预计结束位置可能在行内,因此产生不一样结果。 while (ftell($fh) <= $endPos && !feof($fh)) { yield $raw = fgets($fh); } echo '进程' . $index . '结束时 指针位置:' . ftell($fh) . ', 应该到:' . $endPos . PHP_EOL; fclose($fh); } foreach(range(0,$maxProcess - 1) as $index){ // 多线程采用多线程的方式建立,这里采用yield回调。 foreach(processRead($filename, $index, $singleProcessLength) as $value){ // $value为每一行的内容,处理后释放 unset($value); } }
如上所述就是PHP的大文件读取解决方案,大部分用于多线程读取大文件场景。