最近几天我一直在研究一个奇怪的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 个字符。
- 它已经被清理并用十六进制编辑器仔细检查,没有发现异常字符。
- 当程序认为它已经到达文件末尾时,它总是在第 7172714 行失败(即使它还剩 ~800K 行)。
- 我已经在每行字符较少但行在 20-3000 万行之间没有问题的文件上测试了该程序。
- 我尝试从 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。第 7172713 行后脚本仍然失败7175502,但文件中最后一个用户的唯一 ID 显示在第 7172713 行上。似乎问题是由于某种原因被跳过并且没有被读取。所有换行符都存在。
你必须在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
fgets() 似乎随机读取了一些内容为空的行。该脚本实际上已经到了文件的末尾,即使我的测试显示正在读取的行号由于我进行错误检查的方式(以及错误检查在第三方代码中编写的方式)而落后。现在真正的问题是是什么导致 fgets() 和 fread() 认为一行是空的,即使它不是。我将作为一个单独的问题来问这个问题,因为这是一个话题的变化。谢谢大家的帮助!
此外,为了没有人被挂起,第 3 方代码不起作用的原因是因为它依赖于至少具有换行符的行,其中当前返回空字符串的 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 == "rn" || $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 返回"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无法使用动态方法进行可靠的文件结束计算。只有读者有权确定他是否已到达文件的末尾。
计算最后一个数据块的长度
简要
<?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);
}