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

PHP feof() returning true before the end of file

最近几天我一直在研究一个奇怪的 PHP 问题,feof() 函数在文件结束前 return 为真。下面是我的代码的框架:

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

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

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 在文件结束之前变为 return true 时,我不得不猜测:

A) 某些原因导致 fopen 流失败,然后无法读取任何内容(导致 feof 为 return true)

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

C) PHP众神愤怒了

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

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

您必须拆分文件或增加 php 中的超时 通过:

upload_max_filesize = 2M 
;or whatever size you want

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

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

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

另外,为了不让任何人悬而未决,第 3 方代码不起作用的原因是它依赖于至少有一个换行符的行,而 fgets 和 fread 返回空字符串的当前问题确实存在没有给脚本它需要知道该行曾经存在的内容,因此它会继续尝试执行文件末尾之后的内容。下面是稍微修改过的第 3 方脚本,基于其执行速度,我仍然认为它非常出色。

可以在此处的评论中找到原始脚本: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 
    }
}

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

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

关于空行。

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

也许 fgets returned "false" 并且结果被附加为换行符。 这或许可以解释为什么会出现空行。

另一个原因

test.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 行已被读取,但在其上 feof($res) ===false;。 所以还会有一个迭代。在下一次迭代中(第 6 行)将 return 一个空字符串并且 feof 将 return 为真。

    <?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' 本身不计算任何东西。这是因为 feof 依赖于静态元数据和 readers(是 freadfgetc fgets 等)。 reader 评估是否有指定长度的数据结尾。如果是这样,eof 标志将被设置为 true。如果在$length期间reader没有遇到数据的结尾,那么eof = false。 这种行为是必要的,因为数据可以由其他进程动态添加($mode = 'a +')并且 feof 不能使用动态方法进行稳健的文件结束计算。 reader独自有权判断他是否已经到达文件末尾

正在计算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);
        }