PHP如何读取大文件?下面本篇文章给大家介绍一下利用PHP读取大文件的方法,希望对大家有所帮助!
推荐课程:《Vue + TP6 + Mysql 社交电商系统开发视频课》
API 文档、设计、调试、自动化测试一体化协作工具:点击使用
作为PHP开发人员,我们不需要担心内存管理。 PHP引擎在我们背后进行了出色的清理工作,短暂执行上下文的 web server 模型意味着即使是最草率的代码也没有持久的影响。
在极少数情况下,我们可能需要走出舒适的界限 — 例如,当我们尝试在可以创建的最小 VPS 上为大型项目运行 Composer 时,或者需要在同样小的服务器上读取大文件时。
这是我们将在本教程中讨论的一个问题。
本教程的代码可以在这里找到 GitHub。
衡量成功
唯一能确认我们对代码所做改进是否有效的方式是:衡量一个糟糕的情况,然后对比我们已经应用改进后的衡量情况。换言之,除非我们知道“解决方案”能帮我们到什么程度(如果有的话),否则我们并不知道它是否是一个解决方案。
我们可以关注两个指标。首先是CPU使用率。我们要处理的过程运行得有多快或多慢?其次是内存使用率。脚本执行要占用多少内存?这些通常是成反比的—这意味着我们能够以CPU使用率为代价减少内存的使用率,反之亦可。
在一个异步处理模型(例如多进程或多线程PHP应用程序)中,CPU和内存使用率都是重要的考量。在传统PHP架构中,任一达到服务器所限时这些通常都会成为一个麻烦。
测量PHP内部的CPU使用率是难以实现的。如果你确实关注这一块,可用考虑在Ubuntu或macOS中使用类似于 top
的命令。对于Windows,则可用考虑使用Linux子系统,这样你就能够在Ubuntu中使用 top
命令了。
在本教程中,我们将测量内存使用情况。我们将看一下“传统”脚本会使用多少内存。我们也会实现一些优化策略并对它们进行度量。最后,我希望你能做一个合理的选择。
以下是我们用于查看内存使用量的方法:
// 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]; }
我们将在脚本的结尾处使用这些方法,以便于我们了解哪个脚本一次使用了最多的内存。
我们有什么选择?
我们有许多方法来有效地读取文件。有以下两种场景会使用到他们。我们可能希望同时读取和处理所有数据,对处理后的数据进行输出或者执行其他操作。 我们还可能希望对数据流进行转换而不需要访问到这些数据。
想象以下,对于第一种情况,如果我们希望读取文件并且把每 10,000 行的数据交给单独的队列进行处理。我们则需要至少把 10,000 行的数据加载到内存中,然后把它们交给队列管理器(无论使用哪种)。
对于第二种情况,假设我们想要压缩一个 API 响应的内容,这个 API 响应特别大。虽然这里我们不关心它的内容是什么,但是我们需要确保它被以一种压缩格式备份起来。
这两种情况,我们都需要读取大文件。不同的是,第一种情况我们需要知道数据是什么,而第二种情况我们不关心数据是什么。接下来,让我们来深入讨论一下这两种做法…
逐行读取文件
PHP 处理文件的函数很多,让我们将其中一些函数结合起来实现一个简单的文件阅读器
// 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。内存使用峰值为 12.8 MB。现在,让我们使用生成器来读取每一行:
// 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";
文件大小相同,但是内存使用峰值为 393 KB。这个数据意义大不大,因为我们需要加入对文件数据的处理。例如,当出现两个空白行时,将文档拆分为多个块:
// 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 个字符。
我已经写过 使用生成器提高性能 以及 生成器扩展包,感兴趣的可以去查看