PHP feof() 在文件结束前返回 true

PHP feof() returning true before the end of file

提问人:user2395126 提问时间:1/14/2015 最后编辑:user2395126 更新时间:11/23/2021 访问量:6850

问:

在过去的几天里,我一直在研究一个奇怪的PHP问题,其中feof()函数在文件结束前返回true。下面是我的代码框架:

$this->fh = fopen("bigfile.txt", "r");    

while(!feof($this->fh))
{
    $dataString = fgets($this->fh);

    if($dataString === false && !feof($this->fh))
    {
        echo "Error reading file besides EOF";
    }
    elseif($dataString === false && feof($this->fh))
    {
        echo "We are at the end of the file.\n";

        //check status of the stream
        $meta = stream_get_meta_data($this->fh);
        var_dump($meta);
    }
    else
    {
        //else all is good, process line read in 
    }
}

通过大量测试,我发现该程序在除一个文件之外的所有文件上都运行良好:

  • 该文件存储在本地驱动器上。
  • 该文件大约有 800 万行,平均每行大约 200-500 个字符。
  • 它已经被清理过,并用十六进制编辑器仔细检查,没有发现异常字符。
  • 当程序认为它已到达文件末尾时,它始终在联机7172714失败(即使它还剩下 ~800K 行)。
  • 我已经在每行字符较少但 20-3000 万行之间的文件上测试了该程序,没有问题。
  • 我尝试从 http://php.net/manual/en/function.fgets.php 上的注释运行代码,只是为了查看是否是我的代码中的某些东西导致了问题,并且第三方代码在同一行上失败了。编辑:还值得一提的是,第三方代码使用了 fread() 而不是 fgets()。
  • 我尝试在 fgets 函数中指定几个缓冲区大小,但它们都没有任何区别。

var_dump($meta) 的输出如下:

 array(9) {
  ["wrapper_type"]=>
  string(9) "plainfile"
  ["stream_type"]=>
  string(5) "STDIO"
  ["mode"]=>
  string(1) "r"
  ["unread_bytes"]=>
  int(0)
  ["seekable"]=>
  bool(true)
  ["uri"]=>
  string(65) "full path of file being read"
  ["timed_out"]=>
  bool(false)
  ["blocked"]=>
  bool(true)
  ["eof"]=>
  bool(true)
}

在试图找出导致 feof 在文件结束之前返回 true 的原因时,我必须猜测:

A) 某些东西导致 fopen 流失败,然后无法读取任何内容(导致 feof 返回 true)

B) 某处有一些缓冲区正在填满并造成严重破坏

C) PHP众神生气了

我已经四处搜索,看看是否有其他人遇到这个问题,除了在C++中找不到任何实例,其中文件是通过文本模式而不是二进制模式读取的,并导致了这个问题。

更新: 我让我的脚本不断输出读取函数迭代的次数以及与它旁边找到的条目关联的用户的唯一 ID。7172713出7175502后,脚本仍然失败,但文件中最后一个用户的唯一 ID 显示在第 7172713 行上。似乎问题在于由于某种原因,行被跳过并且没有被读取。所有换行符都存在。

php fopen fgets feof

评论

0赞 Get Off My Lawn 1/14/2015
有没有可能 php 读取文件的内存不足?
0赞 user2395126 1/14/2015
忘了提一下,读取函数是为行块调用的。它读取 500 行,进行一些处理并返回一个值,并将其最后一个位置存储在类范围的变量中。下次调用它时,它会使用类范围变量从上次中断的地方读取接下来的 500 行。一切都使用unset得到妥善处理,在监控服务器内存使用情况时,我没有发现任何异常。因为这太复杂而无法继续测试,所以我编写了这段代码,并在成功读取行时取消设置行读入。仍然看到同样的问题。
0赞 Get Off My Lawn 1/14/2015
您是否尝试过使用 = Read Binary 而不是 Just ?rbr
0赞 user2395126 1/14/2015
不知道您可以在 PHP 中做到这一点,因为它不在 fopen 文档的选项列表中。我现在要尝试一下,如果有效,我会告诉你!
0赞 Get Off My Lawn 1/14/2015
是的,由于某种原因,它并没有真正记录下来,但它是有效的,并且在一些 php.net 示例中使用

答:

4赞 user1000456 1/14/2015 #1

您必须在 PHP 中拆分文件或增加超时 由:

upload_max_filesize = 2M 
;or whatever size you want

max_execution_time = 60 ;此外,如果必须的话,更高

因为: 如果文件指针位于 EOF 或发生错误(包括套接字超时),则返回 TRUE;否则返回 FALSE。 请参见:http://php.net/manual/en/function.feof.php

评论

0赞 user2395126 1/14/2015
超时设置为 72 小时,upload_max_filesize设置为 50G。还值得一提的是,内存限制设置为 2048 MB。
0赞 1/14/2015
它可能是您的文件因安全原因被防病毒软件或防火墙关闭
0赞 user2395126 1/14/2015
我想了想,禁用了一切,没有运气。使用 root 权限运行脚本,看看这是否也有帮助,也没有运气。
0赞 1/14/2015
您的error_reporting是否display_error是否已打开(查看错误)?
0赞 user2395126 1/14/2015
绝对!只需要删除文本内容,因为它是用于用户数据的,您必须相信我,它只是在引号之间是字母数字。[“已编辑”,“已编辑”,“已编辑”,“已编辑”,“已编辑”,“已编辑”,“已编辑”,”
2赞 user2395126 1/14/2015 #2

fgets() 看似随机读取某些内容为空的行。该脚本实际上到达了文件的末尾,即使由于我进行错误检查的方式(以及错误检查在第三方代码中的编写方式)而显示正在读取的行号的测试落后了。现在真正的问题是,是什么导致 fgets() 和 fread() 认为一行是空的,即使它不是空的。我将把它作为一个单独的问题提出,因为这是主题的变化。谢谢大家的帮助!

此外,为了让没有人悬而未决,第三方代码不起作用的原因是因为它依赖于至少具有换行符的行,其中 fgets 和 fread 返回空字符串的当前问题不会为脚本提供它需要知道该行曾经存在过的东西, 因此,它继续尝试在文件末尾执行。下面是略微修改的第三方脚本,根据它的执行速度,我仍然认为它非常出色。

原始脚本可以在评论中找到 这里: http://php.net/manual/en/function.fgets.php 我绝对不认为它值得称赞。

<?php

//File to be opened
$file = "/path/to/file.ext";
//Open file (DON'T USE a+ pointer will be wrong!)
$fp = fopen($file, 'r');
//Read 16meg chunks
$read = 16777216;
//\n Marker
$part = 0;

while(!feof($fp))
{
    $rbuf = fread($fp, $read);
    for($i=$read;$i > 0 || $n == chr(10);$i--)
    {
        $n=substr($rbuf, $i, 1);
        if($n == chr(10))break;
        //If we are at the end of the file, just grab the rest and stop loop
        elseif(feof($fp))
        {
            $i = $read;
            $buf = substr($rbuf, 0, $i+1);
            echo "<EOF>\n";
            break;
        }
    }
    //This is the buffer we want to do stuff with, maybe thow to a function?
    $buf = substr($rbuf, 0, $i+1);

    //output the chunk we just read and mark where it stopped with <break>
    echo $buf . "\n<break>\n";

    //Point marker back to last \n point
    $part = ftell($fp)-($read-($i+1));
    fseek($fp, $part);
}
fclose($fp);

?>

更新:经过数小时的搜索、分析、拔头发等,罪魁祸首似乎是一个未被抓到的坏角色——在这种情况下是 1/2 字符的十六进制值 BD。在生成我从脚本中读取的文件时,使用 stream_get_line() 从其原始源读取该行。然后它应该删除所有坏字符(看来我的正则表达式不符合标准),然后使用 str_getcsv() 将内容转换为数组,进行一些处理,然后写入一个新文件(我试图读取的文件)。在这个过程中的某个地方,可能是 str_getcsv(),1/2 字符导致整个事情只插入一个空行而不是数据。其中数千个被放置在整个文件中(无论 1/2 符号出现在哪里)。这使得文件看起来是正确的长度,但在根据已知行数计算输入时,无法过快地达到 EOF。我要感谢所有帮助我解决这个问题的人,我很抱歉真正的原因与我的问题无关。但是,如果不是每个人的建议和问题,我就不会在正确的地方寻找。

从这次经验中吸取的教训 - 当 EOF 达到太快时,最好的位置是寻找双换行符的实例。在编写从格式化文件中读取的脚本时,一个好的做法是检查这些内容。下面是我修改的原始代码:

$this->fh = fopen("bigfile.txt", "r");    

while(!feof($this->fh))
{
    $dataString = fgets($this->fh);

    if($dataString == "\n" || $dataString == "\r\n" || $dataString == "")
    {
        throw new Exception("Empty line found.");
    }

    if($dataString === false && !feof($this->fh))
    {
        echo "Error reading file besides EOF";
    }
    elseif($dataString === false && feof($this->fh))
    {
        echo "We are at the end of the file.\n";

        //check status of the stream
        $meta = stream_get_meta_data($this->fh);
        var_dump($meta);
    }
    else
    {
        //else all is good, process line read in 
    }
}
0赞 AlexeyP0708 11/23/2021 #3

很多时间已经过去了,但它对其他人有用。

关于第一个问题,我敢假设您的文件共享被拆分为 2 个分区,因为 8M 行 X ~ 每行 200-500 字节 = ~ 1600-4000Mb。你的内存是 2048MB。计算中断在 6M-8M 线或 ~ 7M 之间。

关于空行。

    $str ='hello/r/n';
    echo $str.false; // equivalent to $str. '';

也许 fgets 返回“false”,结果被附加为换行符。 这也许可以解释为什么会出现空行。

另一个原因

测试 .txt

1
2
3
4
5

在示例中,为了清楚起见,我将通过直接指定代码来静态地指示迭代

    <?php
        $res=fopen(__DIR__."/test.txt", "r");
        var_dump('1=>',fread($res,2),feof($res)); //we read 2 bytes each since there is a line feed byte
        var_dump('2=>',fread($res,2),feof($res));
        var_dump('3=>',fread($res,2),feof($res));
        var_dump('4=>',fread($res,2),feof($res));
        var_dump('5=>',fread($res,1),feof($res)); //We read one byte since there is no line feed
        var_dump('6=>',fread($res),feof($res));

结果

string(3) "1=>"
string(2) "1
"
bool(false)
string(3) "2=>"
string(2) "2
"
bool(false)
string(3) "3=>"
string(2) "3
"
bool(false)
string(3) "4=>"
string(2) "4
"
bool(false)
string(3) "5=>"
string(1) "5"
bool(false)
string(3) "6=>"
string(0) ""
bool(true)

我们看到第 5 行被阅读了,但在上面. 所以还会有一次迭代。在下一次迭代中(第 6 行)将返回一个空字符串并将返回 true。feof($res) ===false;feof

    <?php
       $filesize=filesize(__DIR__."/test.txt");
       $res=fopen(__DIR__."/test.txt", "r");
       Echo "----\n";
           var_dump(fread($res,$filesize),feof($res))
           var_dump('fread($res,$filesize),feof($res));
           Echo "----\n";
---
string(9) "1
2
3
4
5"
bool(false)
---
string(0) ""
bool(true)

这些示例显示有一个额外的迭代,因为在读取文件的所有字节时,并不能确定文件的结束。feof

你怎么能解决这样的时刻。

    <?php
       $filesize=filesize(__DIR__."/test.txt")+1;
       $res=fopen(__DIR__."/test.txt", "r");
       var_dump('0=>',fread($res,$filesize),feof($res));

你注意到了吗?我在文件大小值中添加了一个。

就我自己而言,我称 EOF 为“条件结束文件字节”。

就其本身而言,“feof”不计算任何内容。这是因为依赖于静态元数据和读取器(无论是 OR 还是 OR 等)。 读取器评估是否存在指定长度的数据末尾。如果是这样,则标志将设置为 。如果在读取器期间没有达到数据的末尾,则 . 此行为是必要的,因为数据可以由其他进程动态添加 ($ mode = 'a +'),并且 feof 无法使用动态方法执行可靠的文件末尾计算。只有读者有权确定他是否已经到达文件的末尾。feof freadfgetc fgetseoftrue$lengtheof = false

计算 fread 的最后一个数据块的长度

简要

    <?php
        $filesize=filesize(__DIR__."/test.txt");
        $down_size=0;
        $length=8192;
        $data=[];
        $res=fopen(__DIR__."/test.txt", "r");
        $buf='';
        while(!feof($res)){
            if(($down_size+$length)===$filesize){$length++;}
            $buf=fread($res,$length);
            $down_size+=strlen($buf);
        }