如何逐行读取大文件?

Posted

技术标签:

【中文标题】如何逐行读取大文件?【英文标题】:How to read a large file line by line? 【发布时间】:2012-10-26 03:27:33 【问题描述】:

我想逐行读取文件,但没有将其完全加载到内存中。

我的文件太大而无法在内存中打开,如果尝试这样做,我总是会出现内存不足的错误。

文件大小为 1 GB。

【问题讨论】:

在link查看我的回答 你应该使用不带$length参数的fgets() 您想将以下任何一项标记为答案吗? 【参考方案1】:

可以使用fgets()函数逐行读取文件:

$handle = fopen("inputfile.txt", "r");
if ($handle) 
    while (($line = fgets($handle)) !== false) 
        // process the line read.
    

    fclose($handle);
 else 
    // error opening the file.
 

【讨论】:

这对too large to open in memory 部分有何影响? 您没有读取内存中的整个文件。运行它所需的最大内存取决于输入中最长的行。 @Brandin - Moot - 在这些情况下,所提出的问题,即逐行读取文件,没有明确定义的结果。 @ToolmakerSteve 然后定义应该发生的事情。如果您愿意,您可以只打印消息“行太长;放弃”。这也是一个定义明确的结果。 一行可以包含布尔值 false 吗?如果是这样,那么此方法将停止而不会到达文件末尾。此 URL php.net/manual/en/function.fgets.php 上的示例 #1 表明 fgets 有时可以返回布尔值 false,即使尚未到达文件末尾。在该页面的评论部分,人们报告说 fgets() 并不总是返回正确的值,因此使用 feof 作为循环条件更安全。【参考方案2】:
if ($file = fopen("file.txt", "r")) 
    while(!feof($file)) 
        $line = fgets($file);
        # do same stuff with the $line
    
    fclose($file);

【讨论】:

正如@Cuse70 在他的回答中所说,如果文件不存在或无法打开,这将导致无限循环。在 while 循环之前测试 if($file) 我知道这是旧的,但是:不推荐使用 while(!feof($file))。 Have a look here. BTW:“如果文件指针中没有更多数据要读取,则返回 FALSE。” php.net/manual/en/function.fgets.php ...以防万一 feof() 不存在了吗?【参考方案3】:

使用缓冲技术读取文件。

$filename = "test.txt";
$source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
while (!feof($source_file)) 
    $buffer = fread($source_file, 4096);  // use a buffer of 4KB
    $buffer = str_replace($old,$new,$buffer);
    ///

【讨论】:

这值得更多的爱,因为它可以处理大文件,甚至是没有回车或超长行的文件...... 如果 OP 并不真正关心实际线路而只是想例如,我不会感到惊讶。提供下载。在这种情况下,这个答案很好(大多数 PHP 编码人员都会这样做)。 先生,顺便问一下,您将如何在 fopen() 中找到文件?假设我们需要指定打开的url!【参考方案4】:

小心 'while(!feof ... fgets()' 的东西,fgets 可能会出错(返回 false)并永远循环而不会到达文件末尾。 codaddict 最接近正确,但是当你的 ' while fgets 的循环结束,检查 feof;如果不是真的,那么你有一个错误。

【讨论】:

【参考方案5】:

数组返回读取函数

function read_file($filename = '')
    $buffer = array();
    $source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
    while (!feof($source_file)) 
        $buffer[] = fread($source_file, 4096);  // use a buffer of 4KB
    
    return $buffer;

【讨论】:

这将在内存中创建一个超过 1 GB 的数组(祝你好运),甚至不是按行划分,而是按任意 4096 个字符块划分。你到底为什么要这么做?【参考方案6】:

您可以为文件使用面向对象的接口类 - SplFileObject http://php.net/manual/en/splfileobject.fgets.php (PHP 5 >= 5.1.0)

<?php

$file = new SplFileObject("file.txt");

// Loop until we reach the end of the file.
while (!$file->eof()) 
    // Echo one line from the file.
    echo $file->fgets();


// Unset the file to call __destruct(), closing the file handle.
$file = null;

【讨论】:

更清洁的解决方案。谢谢 ;) 还没用过这个类,这里​​有更多有趣的功能可以探索:php.net/manual/en/class.splfileobject.php 谢谢。是的,例如你可以在 while $file->setFlags(SplFileObject::DROP_NEW_LINE); 之前添加这一行为了在行尾删除换行符。 据我所知,SplFileObject 中没有eof() 函数? 谢谢!此外,如果您不想要读取的每个行字符串,请使用 rtrim($file-&gt;fgets()) 去除尾随换行符。 更短:foreach (new SplFileObject('file.txt') as $line) echo $line【参考方案7】:

有一个file() 函数返回文件中包含的行数组。

foreach(file('myfile.txt') as $line) 
   echo $line. "\n";

【讨论】:

1 GB 的文件将全部读入内存并转换为超过 1 GB 的数组...祝你好运。 这不是所提问题的答案,但它确实回答了许多人在查看这里时遇到的更常见的问题,所以它仍然有用,谢谢。 file() 对于处理小文件非常方便。特别是当你想要一个 array() 作为最终结果时。 这对于更大的文件来说是个坏主意,因为整个文件一次被读取到一个数组中 这在大文件上会严重破坏,所以这正是不起作用的方法。【参考方案8】:

此问题的一种流行解决方案将与换行符有关。使用简单的str_replace 可以很容易地修复它。

$handle = fopen("some_file.txt", "r");
if ($handle) 
    while (($line = fgets($handle)) !== false) 
        $line = str_replace("\n", "", $line);
    
    fclose($handle);

【讨论】:

使用 rtrim() 来达到同样的目的更短:) 而且它是跨平台的。【参考方案9】:
foreach (new SplFileObject(__FILE__) as $line) 
    echo $line;

【讨论】:

内存效率与file()相比。【参考方案10】:

如果您要打开一个大文件,您可能希望在 fgets() 旁边使用生成器以避免将整个文件加载到内存中:

/**
 * @return Generator
 */
$fileData = function() 
    $file = fopen(__DIR__ . '/file.txt', 'r');

    if (!$file)
        die('file does not exist or cannot be opened');

    while (($line = fgets($file)) !== false) 
        yield $line;
    

    fclose($file);
;

像这样使用它:

foreach ($fileData() as $line) 
    // $line contains current line

这样您可以在 foreach() 中处理单个文件行。

注意:生成器需要 >= PHP 5.5

【讨论】:

这应该是一个可接受的答案。使用生成器时速度快了一百倍。 而且更节省内存。 @NinoŠkopac:你能解释一下为什么这个解决方案更节省内存吗?例如,与SplFileObject 方法相比。 不确定 Tachi 和 The Onin 的 cmets 与什么进行比较,但我对 90MB 的文本文件运行了这个,与 codadict 的方法相比,发现它慢了 44% 并且使用了相同数量的内存。 (在 PHP 7.3 上运行)【参考方案11】:

这是我管理非常大文件的方式(测试高达 100G)。而且比 fgets() 快

$block =1024*1024;//1MB or counld be any higher than HDD block_size*2
if ($fh = fopen("file.txt", "r"))  
    $left='';
    while (!feof($fh)) // read the file
       $temp = fread($fh, $block);  
       $fgetslines = explode("\n",$temp);
       $fgetslines[0]=$left.$fgetslines[0];
       if(!feof($fh) )$left = array_pop($lines);           
       foreach ($fgetslines as $k => $line) 
           //do smth with $line
        
     

fclose($fh);

【讨论】:

如何保证1024*1024块不会在行中间断掉? @user151496 简单!!数... 1.2.3.4 @OmarElDon 你是什么意思? @user151496 我认为$left 变量就是为了这个目的。缓冲通常更快,因此如果您不介意增加的复杂性,这可能是一个更好的解决方案。【参考方案12】:

SplFileObject 在处理大文件时很有用。

function parse_file($filename)

    try 
        $file = new SplFileObject($filename);
     catch (LogicException $exception) 
        die('SplFileObject : '.$exception->getMessage());
    
    while ($file->valid()) 
        $line = $file->fgets();
        //do something with $line
    

    //don't forget to free the file handle.
    $file = null;

【讨论】:

【参考方案13】:

所有回复中都没有明显的答案。 PHP 有一个简洁的流定界符解析器可用于此目的。

$fp = fopen("/path/to/the/file", "r+");
while (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false) 
  echo $line;

fclose($fp);

【讨论】:

这样我得到了每行末尾的换行符。 @ValterEkholm 是的,每行末尾的换行符变成另一个普通字符,因为分隔符不再是换行符了。【参考方案14】:
<?php
echo '<meta charset="utf-8">';

$k= 1;
$f= 1;
$fp = fopen("texttranslate.txt", "r");
while(!feof($fp)) 
    $contents = '';
    for($i=1;$i<=1500;$i++)
        echo $k.' -- '. fgets($fp) .'<br>';$k++;
        $contents .= fgets($fp);
    
    echo '<hr>';
    file_put_contents('Split/new_file_'.$f.'.txt', $contents);$f++;

?>

【讨论】:

以上是关于如何逐行读取大文件?的主要内容,如果未能解决你的问题,请参考以下文章

在python中逐行读取一个大的压缩文本文件

在python中逐行读取大文件

如何逐行读取大型文本文件,而不将其加载到内存中?

在一个非常大的文件中逐行读取特定的行

Golang逐行读取大文件性能对比

如何在 .NET 中读取大 (1GB) 文本文件?