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