首页 文章

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

提问于
浏览
3

在feof()函数在文件结束之前返回true的最后几天,我一直在研究一个奇怪的PHP问题 . 下面是我的代码的框架:

$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行失败 .

  • 我已经对每行字符数较少但文件在2千万到3千万之间没有问题的文件测试了程序 .

  • 我尝试运行http://php.net/manual/en/function.fgets.php上的注释中的代码,只是为了查看我的代码中是否存在导致问题并且第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行 . 似乎问题出于某种原因被跳过并且未被读取 . 所有换行符都存在 .

2 回答

  • 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

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

相关问题