PHP讀取大文件源碼示例-Swoole多進程讀取大文件

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的大文件讀取解決方案,大部分用於多線程讀取大文件場景。

相關文章
相關標籤/搜索