广告
返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >详解PHP如何读取大文件
  • 282
分享到

详解PHP如何读取大文件

2024-04-02 19:04:59 282人浏览 独家记忆
摘要

目录衡量成功我们有什么选择?逐行读取文件文件之间的管道其他流过滤器自定义流创建自定义协议和过滤器总结衡量成功 唯一能确认我们对代码所做改进是否有效的方式是:衡量一个糟糕的情况,然后对

衡量成功

唯一能确认我们对代码所做改进是否有效的方式是:衡量一个糟糕的情况,然后对比我们已经应用改进后的衡量情况。换言之,除非我们知道 “解决方案” 能帮我们到什么程度 (如果有的话),否则我们并不知道它是否是一个解决方案。

我们可以关注两个指标。首先是 CPU 使用率。我们要处理的过程运行得有多快或多慢?其次是内存使用率。脚本执行要占用多少内存?这些通常是成反比的 — 这意味着我们能够以 CPU 使用率为代价减少内存的使用率,反之亦可。

在一个异步处理模型 (例如多进程或多线程 PHP 应用程序) 中,CPU 和内存使用率都是重要的考量。在传统 php 架构中,任一达到服务器所限时这些通常都会成为一个麻烦。

测量 PHP 内部的 CPU 使用率是难以实现的。如果你确实关注这一块,可用考虑在 ubuntuMacOS 中使用类似于 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 个字符。

生成器还有其他用途,但显然它可以很好的读取大型文件。如果我们需要处理数据,生成器可能是最好的方法。

文件之间的管道

在不需要处理数据的情况下,我们可以将文件数据从一个文件传递到另一个文件。这通常称为管道 (大概是因为除了两端之外,我们看不到管道内的任何东西,当然,只要它是不透明的)。我们可以通过流 (stream) 来实现,首先,我们编写一个脚本实现一个文件到另一个文件的传输,以便我们可以测量内存使用情况:


// from piping-files-1.php
file_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的第二个参数定义了每行要读取的字节数 (默认为-1或到达新行之前的长度)。stream_copy_to_stream 的第三个参数是相同的(默认值完全相同)。stream_copy_to_stream 一次从一个流读取一行,并将其写入另一流。由于我们不需要处理该值,因此它会跳过生成器产生值的部分

单单传输文字还不够实用,所以考虑下其他例子。假设我们想从 CDN 输出图像,可以用以下代码来描述


// from piping-files-3.php
file_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 info
require "memory.php";

想象一下应用程度执行到该步骤。这次我们不是要从本地文件系统中获取图像,而是从 CDN 获取。我们用 file_get_contents 代替更优雅的处理方式 (例如 Guzzle),它们的实际效果是一样的。

内存使用情况为 581KB,现在,我们如何尝试进行流传输呢?


// 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 info
stream_copy_to_stream($handle1, $handle2);
fclose($handle1);
fclose($handle2);
require "memory.php";

内存使用比刚才略少 (400 KB),但是结果是相同的。如果我们不需要内存信息,也可以打印至标准输出。PHP 提供了一种简单的方法来执行此操作:


$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只写,可让我们写入输出缓冲区
  • php://memory与php://temp(可读写) 是临时存储数据的地方。区别在于数据足够大时php:/// temp就会将数据存储在文件系统中,而php:/// memory将继续存储在内存中直到耗尽。

过滤器

我们可以对流使用另一个技巧,称为过滤器。它介于两者之间,对数据进行了适当的控制使其不暴露给外接。假设我们要压缩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 的内存。我们可以使用过滤器来进行优化


// 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 压缩文件有其他诸多好处。但是,你不得不考虑:如果选择其他格式你可以节省 12 倍的内存,你会不会心动?

要对数据进行解压,只需要通过另外一个 zlib 过滤器:


// from filters-2.php
file_get_contents(
    "php://filter/zlib.inflate/resource=filters-2.deflated"
);

自定义流

fopen和file_get_contents具有它们自己的默认选项集,但是它们是完全可定制的。要定义它们,我们需要创建一个新的流上下文


// from creating-contexts-1.php
$data = join("&", [
    "twitter=assertchris",
]);
$headers = join("\r\n", [
    "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);

本例中,我们尝试发送一个 POST 请求给 API。API 端点是安全的,不过我们仍然使用了 http 上下文属性(可用于 http 或者 https)。我们设置了一些头部,并打开了 API 的文件句柄。我们可以将句柄以只读方式打开,上下文负责编写。

创建自定义协议和过滤器

在总结之前,我们先谈谈创建自定义协议。


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 )
}

我们并不打算实现其中一个,因为我认为它值得拥有自己的教程。有很多工作要做。但是一旦完成工作,我们就可以很容易地注册流包装器:


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");

同样,也可以创建自定义流过滤器。


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 字符串中使用自定义过滤器。定义过滤器比定义协议要容易得多。原因之一是协议需要处理目录操作,而过滤器仅需要处理每个数据块。

如果您愿意,我强烈建议您尝试创建自定义协议和过滤器。如果您可以将过滤器应用于 stream_copy_to_stream 操作,则即使处理令人讨厌的大文件,您的应用程序也将几乎不使用任何内存。想象一下编写调整大小图像过滤器或加密应用程序过滤器。

如果你愿意,我强烈建议你尝试创建自定义协议和过滤器。如果你可以将过滤器应用于 stream_copy_to_stream 操作,即使处理烦人的大文件,你的应用程序也几乎不使用任何内存。想象下编写 resize-image 过滤器和 encrypt-for-application 过滤器吧。

总结

虽然这不是我们经常遇到的问题,但是在处理大文件时的确很容易搞砸。在异步应用中,如果我们不注意内存的使用情况,很容易导致服务器的崩溃。

本教程希望能带给你一些新的想法(或者更新你的对这方面的固有记忆),以便你能够更多的考虑如何有效地读取和写入大文件。当我们开始熟悉和使用流和生成器并停止使用诸如 file_get_contents 这样的函数时,这方面的错误将全部从应用程序中消失,这不失为一件好事。

以上就是详解PHP如何读取大文件的详细内容,更多关于PHP如何读取大文件的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解PHP如何读取大文件

本文链接: https://www.lsjlt.com/news/127144.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 详解PHP如何读取大文件
    目录衡量成功我们有什么选择?逐行读取文件文件之间的管道其他流过滤器自定义流创建自定义协议和过滤器总结衡量成功 唯一能确认我们对代码所做改进是否有效的方式是:衡量一个糟糕的情况,然后对...
    99+
    2022-11-12
  • 【PHP】文件写入和读取详解
    一.实现文件读取和写入的基本思路: 1.通过fopen方法打开文件:$fp =fopen(),fp为Resource类型 2.进行文件读取或者文件写入操作(这里使用的函数以1中返回的$fp作为参数)   调用fclose($fp)关闭关闭...
    99+
    2023-09-02
    php 数学建模 开发语言
  • php读取与写入文件(详解)
    作者名:Demo不是emo  主页面链接:主页传送门创作初心:对于计算机的学习者来说,初期的学习无疑是最迷茫和难以坚持的,中后期主要是经验和能力的提高,我也刚接触计算机1年,也在不断的探索,在CSDN写博客主要是为了分享自己的学习历程...
    99+
    2023-08-31
    php 开发语言
  • PHP怎么读取大文件
    小编给大家分享一下PHP怎么读取大文件,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!衡量成功唯一能确认我们对代码所做改进是否有效的方式是:衡量一个糟糕的情况,然后...
    99+
    2023-06-15
  • PHP如何从txt文件中读取数据详解
    目录一、打开/关闭文件二、读写文件1、读取整个文件2、读取一行数据3、读取一个字符4、读取任意长度的字符串总结 一、打开/关闭文件 1、对文件操作时首先要打开文件,打开文件...
    99+
    2022-11-13
  • java如何读取大文件文本
    Java可以使用`BufferedReader`类来读取大文件文本。`BufferedReader`类提供了一个`readLine(...
    99+
    2023-08-08
    java
  • C/C++读取大文件数据方式详细讲解
    目录前言第一种方法第二种方法第三种方法解决前言 以前对C语言与C++不够了解时,我无法知道如何完整获取一个文件的所有数据并且不遗漏掉。 在网络上也搜索了很多很多的相关帖子,但是没有一...
    99+
    2022-11-13
  • python如何流式读取大文件
    这篇文章将为大家详细讲解有关python如何流式读取大文件,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。如何流式读取大文件使用with&hellip;open&hellip;可以从文件中读...
    99+
    2023-06-27
  • PHP如何读取Excel文件
    这篇“PHP如何读取Excel文件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“PHP如何读取Excel文件”文章吧。应该有...
    99+
    2023-06-29
  • python读取大文件内存溢出如何解决
    处理大文件时,可以采用以下方法来避免内存溢出问题:1. 逐行读取:使用文件对象的readline()方法逐行读取文件内容,而不是一次...
    99+
    2023-09-15
    python
  • java读取大文件内存溢出如何解决
    在Java中,如果读取大文件时遇到内存溢出的问题,可以尝试以下几种解决方案:1. 使用缓冲区:使用BufferedReader或者B...
    99+
    2023-08-25
    java
  • Android local.properties 文件读取实例详解
    Android local.properties 文件读取实例详解在Android Studio项目里面有个local.properties文件,这个文件可以放一些系统配置。比如:sdk路径、ndk路径。ndk.dir=D\:\\soft\...
    99+
    2023-05-31
    android local.properties 文件读取
  • Python3读取文件的操作详解
    目录1、引言2、 fileinput2.1 方法介绍2.2 默认读取2.3 处理一个文件2.4 处理批量文件2.5 读取与备份2.5 重定向替换2.6 进阶3、总结1、引言 小鱼:小...
    99+
    2022-11-11
  • SpringBoot读取yaml文件操作详解
    目录1. 单个属性2. 全部属性3. 对象属性补充1. 单个属性 yaml 内的属性如下: server: port: 80 只需在成员变量上注解 @Value(“...
    99+
    2022-11-13
  • php如何读取文件内容
    在PHP中,可以使用`file_get_contents()`函数来读取文件内容。该函数接受一个参数,即要读取的文件的路径,返回文件...
    99+
    2023-08-15
    php
  • PHP如何读取单行文件
    这篇文章给大家分享的是有关PHP如何读取单行文件的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。PHP 读取单行文件 - fgets()fgets() 函数用于从文件读取单行。下例...
    99+
    2022-10-19
  • php如何逐行读取文件
    这篇文章主要讲解了“php如何逐行读取文件”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“php如何逐行读取文件”吧!有一个名为“test.txt”的文本文件,里面的内容为:我们如何逐行读取文...
    99+
    2023-06-20
  • php怎么读取大文件末尾n行
    这篇文章主要介绍“php怎么读取大文件末尾n行”,在日常操作中,相信很多人在php怎么读取大文件末尾n行问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”php怎么读取大文件末尾n行”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-20
  • Python学习之文件的读取详解
    目录文件读取的模式文件对象的读取方法使用 read() 函数一次性读取文件全部内容使用 readlines() 函数 读取文件内容使用 readline() 函数 逐行读取文件内容m...
    99+
    2022-11-13
  • SpringBootyml配置文件读取方法详解
    目录yaml介绍yaml语法规则yaml数据读取Environment读取yaml全部属性数据自定义对象封装指定数据yaml介绍 YAML(YAML Ain't Markup...
    99+
    2022-11-13
    SpringBoot读取yml配置 SpringBoot yml读取
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作