PHP如何讀取大文件?下面本篇文章給大家介紹一下利用PHP讀取大文件的方法,希望對大家有所幫助!
推薦課程:《Vue + TP6 + Mysql 社交電商系統(tǒng)開發(fā)視頻課》
API 文檔、設(shè)計、調(diào)試、自動化測試一體化協(xié)作工具:點擊使用
作為PHP開發(fā)人員,我們不需要擔(dān)心內(nèi)存管理。 PHP引擎在我們背后進行了出色的清理工作,短暫執(zhí)行上下文的 web server 模型意味著即使是最草率的代碼也沒有持久的影響。
在極少數(shù)情況下,我們可能需要走出舒適的界限 — 例如,當(dāng)我們嘗試在可以創(chuàng)建的最小 VPS 上為大型項目運行 Composer 時,或者需要在同樣小的服務(wù)器上讀取大文件時。
這是我們將在本教程中討論的一個問題。
本教程的代碼可以在這里找到 GitHub。
衡量成功
唯一能確認(rèn)我們對代碼所做改進是否有效的方式是:衡量一個糟糕的情況,然后對比我們已經(jīng)應(yīng)用改進后的衡量情況。換言之,除非我們知道“解決方案”能幫我們到什么程度(如果有的話),否則我們并不知道它是否是一個解決方案。
我們可以關(guān)注兩個指標(biāo)。首先是CPU使用率。我們要處理的過程運行得有多快或多慢?其次是內(nèi)存使用率。腳本執(zhí)行要占用多少內(nèi)存?這些通常是成反比的—這意味著我們能夠以CPU使用率為代價減少內(nèi)存的使用率,反之亦可。
在一個異步處理模型(例如多進程或多線程PHP應(yīng)用程序)中,CPU和內(nèi)存使用率都是重要的考量。在傳統(tǒng)PHP架構(gòu)中,任一達到服務(wù)器所限時這些通常都會成為一個麻煩。
測量PHP內(nèi)部的CPU使用率是難以實現(xiàn)的。如果你確實關(guān)注這一塊,可用考慮在Ubuntu或macOS中使用類似于 top
的命令。對于Windows,則可用考慮使用Linux子系統(tǒng),這樣你就能夠在Ubuntu中使用 top
命令了。
在本教程中,我們將測量內(nèi)存使用情況。我們將看一下“傳統(tǒng)”腳本會使用多少內(nèi)存。我們也會實現(xiàn)一些優(yōu)化策略并對它們進行度量。最后,我希望你能做一個合理的選擇。
以下是我們用于查看內(nèi)存使用量的方法:
// formatBytes 方法取材于 php.net 文檔 memory_get_peak_usage(); function formatBytes($bytes, $precision = 2) { $units = array("b", "kb", "mb", "gb", "tb"); $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, $precision) . " " . $units[$pow]; }
我們將在腳本的結(jié)尾處使用這些方法,以便于我們了解哪個腳本一次使用了最多的內(nèi)存。
我們有什么選擇?
我們有許多方法來有效地讀取文件。有以下兩種場景會使用到他們。我們可能希望同時讀取和處理所有數(shù)據(jù),對處理后的數(shù)據(jù)進行輸出或者執(zhí)行其他操作。 我們還可能希望對數(shù)據(jù)流進行轉(zhuǎn)換而不需要訪問到這些數(shù)據(jù)。
想象以下,對于第一種情況,如果我們希望讀取文件并且把每 10,000 行的數(shù)據(jù)交給單獨的隊列進行處理。我們則需要至少把 10,000 行的數(shù)據(jù)加載到內(nèi)存中,然后把它們交給隊列管理器(無論使用哪種)。
對于第二種情況,假設(shè)我們想要壓縮一個 API 響應(yīng)的內(nèi)容,這個 API 響應(yīng)特別大。雖然這里我們不關(guān)心它的內(nèi)容是什么,但是我們需要確保它被以一種壓縮格式備份起來。
這兩種情況,我們都需要讀取大文件。不同的是,第一種情況我們需要知道數(shù)據(jù)是什么,而第二種情況我們不關(guān)心數(shù)據(jù)是什么。接下來,讓我們來深入討論一下這兩種做法…
逐行讀取文件
PHP 處理文件的函數(shù)很多,讓我們將其中一些函數(shù)結(jié)合起來實現(xiàn)一個簡單的文件閱讀器
// from memory.php function formatBytes($bytes, $precision = 2) { $units = array("b", "kb", "mb", "gb", "tb"); $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, $precision) . " " . $units[$pow]; } print formatBytes(memory_get_peak_usage());
// from reading-files-line-by-line-1.php function readTheFile($path) { $lines = []; $handle = fopen($path, "r"); while(!feof($handle)) { $lines[] = trim(fgets($handle)); } fclose($handle); return $lines; } readTheFile("shakespeare.txt"); require "memory.php";
我們正在閱讀一個包括莎士比亞全部著作的文本文件。該文件大小大約為 5.5 MB。內(nèi)存使用峰值為 12.8 MB?,F(xiàn)在,讓我們使用生成器來讀取每一行:
// from reading-files-line-by-line-2.php function readTheFile($path) { $handle = fopen($path, "r"); while(!feof($handle)) { yield trim(fgets($handle)); } fclose($handle); } readTheFile("shakespeare.txt"); require "memory.php";
文件大小相同,但是內(nèi)存使用峰值為 393 KB。這個數(shù)據(jù)意義大不大,因為我們需要加入對文件數(shù)據(jù)的處理。例如,當(dāng)出現(xiàn)兩個空白行時,將文檔拆分為多個塊:
// from reading-files-line-by-line-3.php $iterator = readTheFile("shakespeare.txt"); $buffer = ""; foreach ($iterator as $iteration) { preg_match("/n{3}/", $buffer, $matches); if (count($matches)) { print "."; $buffer = ""; } else { $buffer .= $iteration . PHP_EOL; } } require "memory.php";
有人猜測這次使用多少內(nèi)存嗎?即使我們將文本文檔分為 126 個塊,我們?nèi)匀恢皇褂?459 KB 的內(nèi)存。鑒于生成器的性質(zhì),我們將使用的最大內(nèi)存是在迭代中需要存儲最大文本塊的內(nèi)存。在這種情況下,最大的塊是 101985 個字符。
我已經(jīng)寫過 使用生成器提高性能 以及 生成器擴展包,感興趣的可以去查看