PHP feof() возвращает true до конца файла

Последние несколько дней я работал над странной проблемой 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 
    }
}

После многочисленных испытаний я обнаружил, что программа отлично работает на всем, кроме одного файла:

  • Файл хранится на локальном диске.
  • Этот файл имеет длину около 8 миллионов строк, в среднем около 200-500 символов на строку.
  • Он уже был очищен и при тщательном рассмотрении с помощью шестнадцатеричного редактора не было обнаружено ненормальных символов.
  • Программа последовательно терпит неудачу на линии 7172714, когда она считает, что достигла конца файла (даже если у нее осталось ~800K строк).
  • Я протестировал программу на файлах, которые содержали меньше символов в строке, но содержали от 20 до 30 миллионов строк без проблем.
  • Я попытался запустить код из комментария на 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)

Б) Есть где-то буфер, который заполняет и вызывает хаос

C) PHP боги злы

Я искал повсюду, чтобы увидеть, если кто-то еще имел эту проблему и не может найти никаких экземпляров, кроме как в C++, где файл читался в текстовом режиме, а не в двоичном режиме и вызывал проблему.

ОБНОВЛЕНИЕ: мой скрипт постоянно выводил число повторений функции чтения и уникальный идентификатор пользователя, связанный с записью, найденной рядом с ней. Сценарий все еще не выполняется после строки 7172713 из 7175502, но уникальный идентификатор последнего пользователя в файле отображается в строке 7172713. Кажется, что по какой-то причине строки пропускаются и не читаются. Все разрывы строк присутствуют.

3 ответа

Решение

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);

?>

ОБНОВЛЕНИЕ: После долгих часов поисков, анализа, выдергивания волос и т. Д. Кажется, что преступник был непостижимым плохим персонажем - в данном случае шестнадцатеричное значение BD в 1/2 символа. При создании файла, который я читал из скрипта, использовал 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 
    }
}

Вы должны разделить свой файл или увеличить время ожидания в php на:

upload_max_filesize = 2M 
;or whatever size you want

max_execution_time = 60; Кроме того, выше, если вы должны

Потому что: возвращает TRUE, если указатель файла находится в EOF или произошла ошибка (включая тайм-аут сокета); в противном случае возвращает FALSE. см.: http://php.net/manual/en/function.feof.php

Прошло много времени, но это будет полезно для других.

По поводу 1-го вопроса смею предположить, что ваш файлообменник разбит на 2 раздела, так как 8М строка Х ~200-500 байт на строку = ~1600-4000Мб. Ваша память 2048MB. Расчетное прерывание между 6M-8M строками или ~7M.

О пустых строках.

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

Возможно, fgets вернул "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) вернет пустую строку и вернет 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)

На примерах видно, что есть одна лишняя итерация, потому что в момент, когда все байты файла прочитаны, не определяется конец файла.

Как можно исправить такой момент.

          <?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);
        }
Другие вопросы по тегам