电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

詳解PHP如何讀取大文件

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

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

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

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

測量 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)存中,然后把它們交給隊列管理器(無論使用哪種)。

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

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

逐行讀取文件

PHP 處理文件的函數(shù)很多,讓我們將其中一些函數(shù)結(jié)合起來實現(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。內(nèi)存使用峰值為 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';

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

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

文件之間的管道

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

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

結(jié)果并沒有讓人感到意外。該腳本比其復制的文本文件使用更多的內(nèi)存來運行。這是因為腳本必須在內(nèi)存中讀取整個文件直到將其寫入另外一個文件。對于小的文件而言,這種操作是 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';

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

單單傳輸文字還不夠?qū)嵱茫钥紤]下其他例子。假設我們想從 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),它們的實際效果是一樣的。

內(nèi)存使用情況為 581KB,現(xiàn)在,我們?nèi)绾螄L試進行流傳輸呢?

// 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';

內(nèi)存使用比剛才略少 (400 KB),但是結(jié)果是相同的。如果我們不需要內(nèi)存信息,也可以打印至標準輸出。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只讀,使我們可以訪問原始請求內(nèi)容 php://output只寫,可讓我們寫入輸出緩沖區(qū) php://memory與php://temp(可讀寫) 是臨時存儲數(shù)據(jù)的地方。區(qū)別在于數(shù)據(jù)足夠大時php:/// temp就會將數(shù)據(jù)存儲在文件系統(tǒng)中,而php:/// memory將繼續(xù)存儲在內(nèi)存中直到耗盡。過濾器

我們可以對流使用另一個技巧,稱為過濾器。它介于兩者之間,對數(shù)據(jù)進行了適當?shù)目刂剖蛊洳槐┞督o外接。假設我們要壓縮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 的內(nèi)存。我們可以使用過濾器來進行優(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過濾器,該過濾器讀取和壓縮資源的內(nèi)容。然后我們可以將該壓縮數(shù)據(jù)通過管道傳輸?shù)搅硪粋€文件中。這僅使用了 896KB 內(nèi)存。

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

要對數(shù)據(jù)進行解壓,只需要通過另外一個 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 端點是安全的,不過我們?nèi)匀皇褂昧?http 上下文屬性(可用于 http 或者 https)。我們設置了一些頭部,并打開了 API 的文件句柄。我們可以將句柄以只讀方式打開,上下文負責編寫。

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

在總結(jié)之前,我們先談談創(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é)議需要處理目錄操作,而過濾器僅需要處理每個數(shù)據(jù)塊。

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

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

總結(jié)

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

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

以上就是詳解PHP如何讀取大文件的詳細內(nèi)容,更多關(guān)于PHP如何讀取大文件的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標簽: PHP
相關(guān)文章:
主站蜘蛛池模板: 杭州厂房降温,车间降温设备,车间通风降温,厂房降温方案,杭州嘉友实业爽风品牌 | 螺旋压榨机-刮泥机-潜水搅拌机-电动泥斗-潜水推流器-南京格林兰环保设备有限公司 | Safety light curtain|Belt Sway Switches|Pull Rope Switch|ultrasonic flaw detector-Shandong Zhuoxin Machinery Co., Ltd | 臻知网大型互动问答社区-你的问题将在这里得到解答!-无锡据风网络科技有限公司 | 工业设计,人工智能,体验式3D展示的智能技术交流服务平台-纳金网 J.S.Bach 圣巴赫_高端背景音乐系统_官网 | 合肥注册公司|合肥代办营业执照、2024注册公司流程 | 智能风向风速仪,风速告警仪,数字温湿仪,综合气象仪(气象五要素)-上海风云气象仪器有限公司 | 安平县鑫川金属丝网制品有限公司,声屏障,高速声屏障,百叶孔声屏障,大弧形声屏障,凹凸穿孔声屏障,铁路声屏障,顶部弧形声屏障,玻璃钢吸音板 | 污水处理设备-海普欧环保集团有限公司 | 碳刷_刷握_集电环_恒压簧_电刷厂家-上海丹臻机电科技有限公司 | 砂石生产线_石料生产线设备_制砂生产线设备价格_生产厂家-河南中誉鼎力智能装备有限公司 | 汽液过滤网厂家_安平县银锐丝网有限公司 | 钢木实验台-全钢实验台-化验室通风柜-实验室装修厂家-杭州博扬实验设备 | 登车桥动力单元-非标液压泵站-非标液压系统-深圳市三好科技有限公司 | 河南凯邦机械制造有限公司| 烟气在线监测系统_烟气在线监测仪_扬尘检测仪_空气质量监测站「山东风途物联网」 | 滚珠丝杆升降机_螺旋升降机_丝杠升降机-德迈传动 | 济南电缆桥架|山东桥架-济南航丰实业有限公司 | 专业生产动态配料系统_饲料配料系统_化肥配料系统等配料系统-郑州鑫晟重工机械有限公司 | 玻璃钢板-玻璃钢防腐瓦-玻璃钢材料-广东壹诺 | BAUER减速机|ROSSI-MERSEN熔断器-APTECH调压阀-上海爱泽工业设备有限公司 | 杭州营业执照代办-公司变更价格-许可证办理流程_杭州福道财务管理咨询有限公司 | 电磁流量计_智能防腐防爆管道式计量表-金湖凯铭仪表有限公司 | 浙江美尔凯特智能厨卫股份有限公司 | 蓄电池在线监测系统|SF6在线监控泄露报警系统-武汉中电通电力设备有限公司 | 开云(中国)Kaiyun·官方网站-登录入口 | 五轴加工中心_数控加工中心_铝型材加工中心-罗威斯 | 深圳办公室装修-写字楼装修设计-深圳标榜装饰公司 | 日本SMC气缸接头-速度控制阀-日本三菱伺服电机-苏州禾力自动化科技有限公司 | 安全光栅|射频导纳物位开关|音叉料位计|雷达液位计|两级跑偏开关|双向拉绳开关-山东卓信机械有限公司 | 大米加工设备|大米加工机械|碾米成套设备|大米加工成套设备-河南成立粮油机械有限公司 | 石膏基自流平砂浆厂家-高强石膏基保温隔声自流平-轻质抹灰石膏粉砂浆批发-永康市汇利建设有限公司 | 沈阳庭院景观设计_私家花园_别墅庭院设计_阳台楼顶花园设计施工公司-【沈阳现代时园艺景观工程有限公司】 | 成都离婚律师|成都结婚律师|成都离婚财产分割律师|成都律师-成都离婚律师网 | 洗瓶机厂家-酒瓶玻璃瓶冲瓶机-瓶子烘干机-封口旋盖压盖打塞机_青州惠联灌装机械 | 危废处理系统,水泥厂DCS集散控制系统,石灰窑设备自动化控制系统-淄博正展工控设备 | 杭州货架订做_组合货架公司_货位式货架_贯通式_重型仓储_工厂货架_货架销售厂家_杭州永诚货架有限公司 | 济南品牌包装设计公司_济南VI标志设计公司_山东锐尚文化传播 | 岸电电源-60HZ变频电源-大功率变频电源-济南诚雅电子科技有限公司 | 健康管理师报名入口,2025年健康管理师考试时间信息网-网站首页 塑料造粒机「厂家直销」-莱州鑫瑞迪机械有限公司 | 时代北利离心机,实验室离心机,医用离心机,低速离心机DT5-2,美国SKC采样泵-上海京工实业有限公司 工业电炉,台车式电炉_厂家-淄博申华工业电炉有限公司 |