如何逐行读取大文件?
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->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++;
?>
【讨论】:
以上是关于如何逐行读取大文件?的主要内容,如果未能解决你的问题,请参考以下文章