日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

詳解PHP如何讀取大文件

瀏覽:22日期:2022-09-07 09:29:29
目錄衡量成功我們有什么選擇?逐行讀取文件文件之間的管道其他流過濾器自定義流創(chuàng)建自定義協(xié)議和過濾器總結衡量成功

唯一能確認我們對代碼所做改進是否有效的方式是:衡量一個糟糕的情況,然后對比我們已經應用改進后的衡量情況。換言之,除非我們知道 “解決方案” 能幫我們到什么程度 (如果有的話),否則我們并不知道它是否是一個解決方案。

我們可以關注兩個指標。首先是 CPU 使用率。我們要處理的過程運行得有多快或多慢?其次是內存使用率。腳本執(zhí)行要占用多少內存?這些通常是成反比的 — 這意味著我們能夠以 CPU 使用率為代價減少內存的使用率,反之亦可。

在一個異步處理模型 (例如多進程或多線程 PHP 應用程序) 中,CPU 和內存使用率都是重要的考量。在傳統(tǒng) PHP 架構中,任一達到服務器所限時這些通常都會成為一個麻煩。

測量 PHP 內部的 CPU 使用率是難以實現(xiàn)的。如果你確實關注這一塊,可用考慮在 Ubuntu 或 macOS 中使用類似于 top 的命令。對于 Windows,則可用考慮使用 Linux 子系統(tǒng),這樣你就能夠在 Ubuntu 中使用 top 命令了。

在本教程中,我們將測量內存使用情況。我們將看一下 “傳統(tǒng)” 腳本會使用多少內存。我們也會實現(xiàn)一些優(yōu)化策略并對它們進行度量。最后,我希望你能做一個合理的選擇。

以下是我們用于查看內存使用量的方法:

// 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];}

我們將在腳本的結尾處使用這些方法,以便于我們了解哪個腳本一次使用了最多的內存。

我們有什么選擇?

我們有許多方法來有效地讀取文件。有以下兩種場景會使用到他們。我們可能希望同時讀取和處理所有數據,對處理后的數據進行輸出或者執(zhí)行其他操作。 我們還可能希望對數據流進行轉換而不需要訪問到這些數據。

想象以下,對于第一種情況,如果我們希望讀取文件并且把每 10,000 行的數據交給單獨的隊列進行處理。我們則需要至少把 10,000 行的數據加載到內存中,然后把它們交給隊列管理器(無論使用哪種)。

對于第二種情況,假設我們想要壓縮一個 API 響應的內容,這個 API 響應特別大。雖然這里我們不關心它的內容是什么,但是我們需要確保它被以一種壓縮格式備份起來。

這兩種情況,我們都需要讀取大文件。不同的是,第一種情況我們需要知道數據是什么,而第二種情況我們不關心數據是什么。接下來,讓我們來深入討論一下這兩種做法.

逐行讀取文件

PHP 處理文件的函數很多,讓我們將其中一些函數結合起來實現(xiàn)一個簡單的文件閱讀器

// from memory.phpfunction 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.phpfunction 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。內存使用峰值為 12.8 MB。現(xiàn)在,讓我們使用生成器來讀取每一行:

// from reading-files-line-by-line-2.phpfunction readTheFile($path) { $handle = fopen($path, 'r'); while(!feof($handle)) {yield trim(fgets($handle)); } fclose($handle);}readTheFile('shakespeare.txt');require 'memory.php';

文件大小相同,但是內存使用峰值為 393 KB。這個數據意義大不大,因為我們需要加入對文件數據的處理。例如,當出現(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';

有人猜測這次使用多少內存嗎?即使我們將文本文檔分為 126 個塊,我們仍然只使用 459 KB 的內存。鑒于生成器的性質,我們將使用的最大內存是在迭代中需要存儲最大文本塊的內存。在這種情況下,最大的塊是 101985 個字符。

生成器還有其他用途,但顯然它可以很好的讀取大型文件。如果我們需要處理數據,生成器可能是最好的方法。

文件之間的管道

在不需要處理數據的情況下,我們可以將文件數據從一個文件傳遞到另一個文件。這通常稱為管道 (大概是因為除了兩端之外,我們看不到管道內的任何東西,當然,只要它是不透明的)。我們可以通過流 (stream) 來實現(xiàn),首先,我們編寫一個腳本實現(xiàn)一個文件到另一個文件的傳輸,以便我們可以測量內存使用情況:

// from piping-files-1.phpfile_put_contents( 'piping-files-1.txt', file_get_contents('shakespeare.txt'));require 'memory.php';

結果并沒有讓人感到意外。該腳本比其復制的文本文件使用更多的內存來運行。這是因為腳本必須在內存中讀取整個文件直到將其寫入另外一個文件。對于小的文件而言,這種操作是 OK 的。但是將其用于大文件時,就不是那么回事了。

讓我們嘗試從一個文件流式傳輸 (或管道傳輸) 到另一個文件:

// from piping-files-2.php$handle1 = fopen('shakespeare.txt', 'r');$handle2 = fopen('piping-files-2.txt', 'w');stream_copy_to_stream($handle1, $handle2);fclose($handle1);fclose($handle2);require 'memory.php';

這段代碼有點奇怪。我們打開兩個文件的句柄,第一個處于讀取模式,第二個處于寫入模式。然后,我們從第一個復制到第二個。我們通過再次關閉兩個文件來完成。當你知道內存使用為 393 KB 時,可能會感到驚訝。這個數字看起來很熟悉,這不就是利用生成器保存逐行讀取內容時所使用的內存嗎。這是因為fgets的第二個參數定義了每行要讀取的字節(jié)數 (默認為-1或到達新行之前的長度)。stream_copy_to_stream 的第三個參數是相同的(默認值完全相同)。stream_copy_to_stream 一次從一個流讀取一行,并將其寫入另一流。由于我們不需要處理該值,因此它會跳過生成器產生值的部分

單單傳輸文字還不夠實用,所以考慮下其他例子。假設我們想從 CDN 輸出圖像,可以用以下代碼來描述

// from piping-files-3.phpfile_put_contents( 'piping-files-3.jpeg', file_get_contents('https://github.com/assertchris/uploads/raw/master/rick.jpg' ));// ...or write this straight to stdout, if we don’t need the memory inforequire 'memory.php';

想象一下應用程度執(zhí)行到該步驟。這次我們不是要從本地文件系統(tǒng)中獲取圖像,而是從 CDN 獲取。我們用 file_get_contents 代替更優(yōu)雅的處理方式 (例如 Guzzle),它們的實際效果是一樣的。

內存使用情況為 581KB,現(xiàn)在,我們如何嘗試進行流傳輸呢?

// from piping-files-4.php$handle1 = fopen('https://github.com/assertchris/uploads/raw/master/rick.jpg', 'r');$handle2 = fopen('piping-files-4.jpeg', 'w');// ...or write this straight to stdout, if we don’t need the memory infostream_copy_to_stream($handle1, $handle2);fclose($handle1);fclose($handle2);require 'memory.php';

內存使用比剛才略少 (400 KB),但是結果是相同的。如果我們不需要內存信息,也可以打印至標準輸出。PHP 提供了一種簡單的方法來執(zhí)行此操作:

$handle1 = fopen('https://github.com/assertchris/uploads/raw/master/rick.jpg', 'r');$handle2 = fopen('php://stdout', 'w');stream_copy_to_stream($handle1, $handle2);fclose($handle1);fclose($handle2);// require 'memory.php';其他流

還存在一些流可以通過管道來讀寫。

php://stdin只讀 php://stderr只寫,與php://stdout相似 php://input只讀,使我們可以訪問原始請求內容 php://output只寫,可讓我們寫入輸出緩沖區(qū) php://memory與php://temp(可讀寫) 是臨時存儲數據的地方。區(qū)別在于數據足夠大時php:/// temp就會將數據存儲在文件系統(tǒng)中,而php:/// memory將繼續(xù)存儲在內存中直到耗盡。過濾器

我們可以對流使用另一個技巧,稱為過濾器。它介于兩者之間,對數據進行了適當的控制使其不暴露給外接。假設我們要壓縮shakespeare.txt文件。我們可以使用 Zip 擴展

// from filters-1.php$zip = new ZipArchive();$filename = 'filters-1.zip';$zip->open($filename, ZipArchive::CREATE);$zip->addFromString('shakespeare.txt', file_get_contents('shakespeare.txt'));$zip->close();require 'memory.php';

這段代碼雖然整潔,但是總共使用了大概 10.75 MB 的內存。我們可以使用過濾器來進行優(yōu)化

// from filters-2.php$handle1 = fopen('php://filter/zlib.deflate/resource=shakespeare.txt', 'r');$handle2 = fopen('filters-2.deflated', 'w');stream_copy_to_stream($handle1, $handle2);fclose($handle1);fclose($handle2);require 'memory.php';

在這里,我們可以看到php:///filter/zlib.deflate過濾器,該過濾器讀取和壓縮資源的內容。然后我們可以將該壓縮數據通過管道傳輸到另一個文件中。這僅使用了 896KB 內存。

雖然格式不同,或者說使用 zip 壓縮文件有其他諸多好處。但是,你不得不考慮:如果選擇其他格式你可以節(jié)省 12 倍的內存,你會不會心動?

要對數據進行解壓,只需要通過另外一個 zlib 過濾器:

// from filters-2.phpfile_get_contents( 'php://filter/zlib.inflate/resource=filters-2.deflated');自定義流

fopen和file_get_contents具有它們自己的默認選項集,但是它們是完全可定制的。要定義它們,我們需要創(chuàng)建一個新的流上下文

// from creating-contexts-1.php$data = join('&', [ 'twitter=assertchris',]);$headers = join('rn', [ 'Content-type: application/x-www-form-urlencoded', 'Content-length: ' . strlen($data),]);$options = [ 'http' => ['method' => 'POST','header'=> $headers,'content' => $data, ],];$context = stream_content_create($options);$handle = fopen('https://example.com/register', 'r', false, $context);$response = stream_get_contents($handle);fclose($handle);

本例中,我們嘗試發(fā)送一個 POST 請求給 API。API 端點是安全的,不過我們仍然使用了 http 上下文屬性(可用于 http 或者 https)。我們設置了一些頭部,并打開了 API 的文件句柄。我們可以將句柄以只讀方式打開,上下文負責編寫。

創(chuàng)建自定義協(xié)議和過濾器

在總結之前,我們先談談創(chuàng)建自定義協(xié)議。

Protocol { public resource $context; public __construct ( void ) public __destruct ( void ) public bool dir_closedir ( void ) public bool dir_opendir ( string $path , int $options ) public string dir_readdir ( void ) public bool dir_rewinddir ( void ) public bool mkdir ( string $path , int $mode , int $options ) public bool rename ( string $path_from , string $path_to ) public bool rmdir ( string $path , int $options ) public resource stream_cast ( int $cast_as ) public void stream_close ( void ) public bool stream_eof ( void ) public bool stream_flush ( void ) public bool stream_lock ( int $operation ) public bool stream_metadata ( string $path , int $option , mixed $value ) public bool stream_open ( string $path , string $mode , int $options ,string &$opened_path ) public string stream_read ( int $count ) public bool stream_seek ( int $offset , int $whence = SEEK_SET ) public bool stream_set_option ( int $option , int $arg1 , int $arg2 ) public array stream_stat ( void ) public int stream_tell ( void ) public bool stream_truncate ( int $new_size ) public int stream_write ( string $data ) public bool unlink ( string $path ) public array url_stat ( string $path , int $flags )}

我們并不打算實現(xiàn)其中一個,因為我認為它值得擁有自己的教程。有很多工作要做。但是一旦完成工作,我們就可以很容易地注冊流包裝器:

if (in_array('highlight-names', stream_get_wrappers())) { stream_wrapper_unregister('highlight-names');}stream_wrapper_register('highlight-names', 'HighlightNamesProtocol');$highlighted = file_get_contents('highlight-names://story.txt');

同樣,也可以創(chuàng)建自定義流過濾器。

Filter { public $filtername; public $params public int filter ( resource $in , resource $out , int &$consumed ,bool $closing ) public void onClose ( void ) public bool onCreate ( void )}

可被輕松注冊

$handle = fopen('story.txt', 'w+');stream_filter_append($handle, 'highlight-names', STREAM_FILTER_READ);

highlight-names 需要與新過濾器類的 filtername 屬性匹配。還可以在 php:///filter/highligh-names/resource=story.txt 字符串中使用自定義過濾器。定義過濾器比定義協(xié)議要容易得多。原因之一是協(xié)議需要處理目錄操作,而過濾器僅需要處理每個數據塊。

如果您愿意,我強烈建議您嘗試創(chuàng)建自定義協(xié)議和過濾器。如果您可以將過濾器應用于 stream_copy_to_stream 操作,則即使處理令人討厭的大文件,您的應用程序也將幾乎不使用任何內存。想象一下編寫調整大小圖像過濾器或加密應用程序過濾器。

如果你愿意,我強烈建議你嘗試創(chuàng)建自定義協(xié)議和過濾器。如果你可以將過濾器應用于 stream_copy_to_stream 操作,即使處理煩人的大文件,你的應用程序也幾乎不使用任何內存。想象下編寫 resize-image 過濾器和 encrypt-for-application 過濾器吧。

總結

雖然這不是我們經常遇到的問題,但是在處理大文件時的確很容易搞砸。在異步應用中,如果我們不注意內存的使用情況,很容易導致服務器的崩潰。

本教程希望能帶給你一些新的想法(或者更新你的對這方面的固有記憶),以便你能夠更多的考慮如何有效地讀取和寫入大文件。當我們開始熟悉和使用流和生成器并停止使用諸如 file_get_contents 這樣的函數時,這方面的錯誤將全部從應用程序中消失,這不失為一件好事。

以上就是詳解PHP如何讀取大文件的詳細內容,更多關于PHP如何讀取大文件的資料請關注好吧啦網其它相關文章!

標簽: PHP
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产精品传媒麻豆hd| 日韩精品久久久久久久电影99爱| 黄色成人91| 亚洲婷婷在线| 欧美精品九九| 老色鬼久久亚洲一区二区| 视频一区国产视频| 首页国产欧美日韩丝袜| 亚洲免费福利一区| 天堂av在线一区| 免费观看在线综合色| 亚洲毛片在线免费| 国产精品乱战久久久| 国内精品美女在线观看| 99久久久国产精品美女| 丝瓜av网站精品一区二区| 亚洲毛片在线免费| 国产精品黄色片| 中文在线免费视频| 日韩午夜av| 亚洲免费毛片| 麻豆久久一区二区| 91精品精品| 亚洲综合激情在线| 国产精品一在线观看| 亚洲永久av| 亚洲色诱最新| 国产精品一区二区精品| 日韩av在线播放网址| 黄色不卡一区| 日韩激情综合| 最新中文字幕在线播放| 亚洲专区视频| 国产乱人伦丫前精品视频 | 国产精品自拍区| 麻豆国产精品一区二区三区| 日韩在线欧美| 免费观看日韩电影| 精品国产亚洲一区二区在线观看| 在线一区视频观看| 最近国产精品视频| 成人在线视频中文字幕| 一区在线免费观看| 国产欧美大片| 久久亚洲国产| 欧美日韩一区二区三区不卡视频| 黑人精品一区| 亚洲免费专区| 九九精品调教| 日韩av黄色在线| 日韩中文首页| 日本不卡视频在线| 日韩免费看片| 日本午夜精品久久久久| 日韩久久精品| 日韩国产成人精品| 欧美成人基地 | 女人av一区| 久久激情综合网| 91精品高清| 精品久久久中文字幕| 国产精品嫩草99av在线| 欧美国产视频| 免费在线视频一区| 成人小电影网站| 国产调教精品| 亚洲欧美日韩国产一区| 欧美国产一级| 日韩av影院| 999久久久国产精品| 国产欧美一区二区三区国产幕精品 | 成人一区而且| 无码日韩精品一区二区免费| 婷婷激情一区| 国产极品模特精品一二| 午夜在线视频一区二区区别| 亚洲精品福利电影| 国产精品15p| 午夜久久av| 9色国产精品| 欧产日产国产精品视频| 国产精品日韩精品中文字幕| 国产精品视区| 亚洲精品国产嫩草在线观看| 国产精品nxnn| 日本在线一区二区三区| 好吊日精品视频| 日本不卡免费高清视频在线| 欧美亚洲自偷自偷| 中文不卡在线| 亚洲一区黄色| 久久三级视频| 伊人网在线播放| 久久一区精品| 国产乱论精品| 国产毛片精品| 免费久久99精品国产自在现线| 国产一区2区| 国产精品66| 91成人在线| 蜜臀久久久久久久| 久久久国产精品一区二区中文| 精品网站999| 欧美成人精品午夜一区二区| 日韩欧美四区| 亚洲理论在线| 在线视频亚洲欧美中文| 合欧美一区二区三区| 久久久久久久久久久9不雅视频| 九九九精品视频| 国产精品s色| 国产精久久一区二区| 国产精品美女在线观看直播| 国产日产精品一区二区三区四区的观看方式 | 国产白浆在线免费观看| 国产精品巨作av| 青青伊人久久| 日韩国产高清在线| 欧美一区免费| 国产亚洲欧美日韩在线观看一区二区 | 国产精品免费99久久久| 国产精品毛片aⅴ一区二区三区| 欧美一区不卡| 日本h片久久| 欧美亚洲二区| 国产精品久久久久久模特| 国产欧美丝祙| 国产精品一卡| 久久三级中文| 麻豆成全视频免费观看在线看| а√天堂中文在线资源8| 美女福利一区二区三区| 九九精品调教| 日韩视频在线一区二区三区 | 亚洲高清影视| 午夜宅男久久久| 婷婷久久免费视频| 日本a级不卡| 久久精品国产福利| 欧洲一区二区三区精品| 99精品在线免费在线观看| 国产精品毛片在线| 日韩专区视频网站| 国产精品porn| 日本精品不卡| 亚洲欧美激情诱惑| 91亚洲无吗| 国产+成+人+亚洲欧洲在线| 成人免费电影网址| 亚洲国产不卡| 日本成人在线不卡视频| 精品国产日韩欧美精品国产欧美日韩一区二区三区 | 久久精品资源| 欧美特黄一级大片| 国产麻豆精品| 吉吉日韩欧美| 米奇777超碰欧美日韩亚洲| 亚洲精品成人| 亚洲欧美网站在线观看| 国产精品最新自拍| 亚洲日本网址| 自拍自偷一区二区三区| 亚洲精品国产日韩| 精品国内亚洲2022精品成人| 欧美色图一区| 日本国产欧美| 国产日韩电影| 亚洲精品美女| 在线看片国产福利你懂的| 日韩一级网站| 国产精品一二| 91成人精品视频| 久久精品xxxxx| 91精品一区国产高清在线gif| 亚洲精品激情| 91综合网人人| 亚洲永久精品唐人导航网址| 国产伦精品一区二区三区在线播放| 涩涩av在线| 日韩欧美中文字幕电影| 免费高潮视频95在线观看网站| 中文一区一区三区免费在线观| 久久亚洲人体| 免费在线观看不卡| 激情国产在线| 日本v片在线高清不卡在线观看| 成人三级高清视频在线看| 最近国产精品视频| 欧洲av不卡| 欧美一区91| 欧美少妇精品| 久久福利精品| 卡一精品卡二卡三网站乱码| 日韩亚洲国产欧美| 精品国产91| 亚洲精品影视| 亚洲天堂一区二区| 国产亚洲高清在线观看| 99国产精品视频免费观看一公开 | 日韩区一区二|