3

过去几天我一直在研究一个奇怪的 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 个字符。
  • 它已经被清理过,并用十六进制编辑器仔细检查,没有发现异常字符。
  • 当程序认为它已经到达文件末尾时(即使它还剩下大约 800K 行),程序在第 7172714 行始终失败。
  • 我已经在每行字符较少但在 20-30 百万行之间没有问题的文件上测试了该程序。
  • 我尝试从http://php.net/manual/en/function.fgets.php上的评论中运行代码,以查看是否是我的代码中的某些内容导致了问题,而第 3 方代码同样失败线。编辑:还值得一提的是,第 3 方代码使用 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。该脚本在 7175502 中的第 7172713 行之后仍然失败,但文件中最后一个用户的唯一 ID 显示在第 7172713 行。似乎问题是由于某种原因,行被跳过并且未被读取。所有换行符都存在。

4

3 回答 3

4

您必须通过以下方式拆分文件或增加 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

于 2015-01-14T05:24:22.980 回答
2

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 
    }
}
于 2015-01-14T06:57:25.577 回答
0

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

关于第一个问题,我敢假设您的文件共享分为 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 行被读取,但是在它上面feof($res) ===false;。所以会有更多的迭代。并且在下一次迭代中(第 6 行)将返回一个空字符串feof并将返回 true。

    <?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依赖于静态元数据和阅读器(无论是它 fread还是fgetc fgets其他)。阅读器评估是否有指定长度的数据结尾。如果是这样,eof标志将设置为true。如果在读期间$length还没有遇到数据的结尾,那么eof = false。这种行为是必要的,因为数据可以由其他进程动态添加($ mode = 'a +'),并且 feof 无法使用动态方法进行稳健的文件结束计算。只有读者有权确定他是否已到达文件末尾。

计算 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);
        }
于 2021-11-22T16:28:50.460 回答