PHP读取大文件大小的方法---注意fgets()指针越界.

jackxiang 2010-9-13 15:21 | |
string fgets ( int $handle [, int $length ] )
从 handle 指向的文件中读取一行并返回长度最多为 length - 1 字节的字符串。碰到换行符(包括在返回值中)、EOF 或者已经读取了 length - 1 字节后停止(看先碰到那一种情况)。如果没有指定 length ,则默认为 1K,或者说 1024 字节。
bool feof ( resource $handle )
果传递的文件指针无效可能会陷入无限循环中,因为 EOF 不会返回 TRUE。
先上代码,正确的写法:
指针位置文件:line_number_mark.txt,需要读取N行的大文件:onlyQQFilteredByJack.txt

<?php
$num = 0;
$insert_num = 10; //insert amount every time.
if(file_exists('line_number_mark.txt'))
{
$num = trim(file_get_contents($rootPath . 'line_number_mark.txt'));
}
echo "pointer_num=".$num."\n";
$fp = fopen('./onlyQQFilteredByJack.txt', 'r');
fseek($fp, $num);
$i = 0;
while($i < $insert_num)
{
    if(!feof($fp))
    {
        $data = fgets($fp);
        $value = trim($data);
        if(!empty($value))
        {
            $i++;
            echo $value."\n";
        }
    }else
    {
        echo "out of the file's bindery,exit the while loop..\n";
        $position = ftell($fp);
        $flag = 1;//set flag means it's already end
        break;//Break 退出循环
    }
}
if($flag)
file_put_contents($rootPath . 'line_number_mark.txt', $position);
else
file_put_contents($rootPath . 'line_number_mark.txt', ftell($fp));
?>

指针的越位,错误的写法:
<?php
$num = 0;
$insert_num = 10; //insert amount every time.
if(file_exists($rootPath . 'line_number_mark.txt'))
{
        $num = trim(file_get_contents($rootPath . 'line_number_mark.txt'));
}
echo "num=".$num."\n";
$fp = fopen($rootPath . 'onlyQQFilteredByJack.txt', 'r');
fseek($fp, $num);
echo ftell($fp)."\n";
if(feof($fp))
{
        echo "Out of the file's bond\n";
        exit();
}
$i = 0;
while($i < $insert_num)
{
    $data = fgets($fp);//没fget一次,指针自动加一,后面ftell的值也是加一后的值,而在while下造成了越界,下次在来读取后在上面的if(feof($fp))出现传递的文件指针无效可能会陷入无限循环中,因为 EOF 不会返回 TRUE。
    $value = trim($data);
    if(!empty($value))
    {
        $i++;
        echo $value."\n";
    }
}
echo ftell($fp)."\n";
file_put_contents($rootPath . 'line_number_mark.txt', ftell($fp));
?>

回忆未来(372647693)  14:52:20
PHP读取大文件,一次读取,php的内存往往不够用,大家如何解决类似的问题?
膘叔(19129540)  14:52:51
那就别一次读。
莫莫(3296320)  14:53:24
用fopen
莫莫(3296320)  14:53:30
然后一点一点移指针
PHP内置函数filesize()返回文件的长度是int类型的,按照PHP文档上的sprintf('%u', filesize($str_filename)),最大也只能正确表示不大于2748436857字节(约2.6GB)。解决办法有不少,整理了最通用的办法如下:
在本例中, $int_filesize实际上是以浮点型存储的,但应理解为整数
Linux系统
$int_filesize = 0 + trim(`stat -c%s $str_file`);
Windows系统
$int_size = 0 + exec('FOR %A IN ("'. $str_file.'") DO @ECHO %~zA');
完整函数
function big_filesize($str_filename)
{
    $int_filesize = filesize($str_filename);
    if($int_filesize < 0){
        if(strtolower(substr($_SERVER['OS'], 0, 3)) == 'win'){
            $int_size = 0 + exec('FOR %A IN ("'. $str_file.'") DO @ECHO %~zA');
        }else{
            $int_filesize = 0 + trim(`stat -c%s $str_file`);
        }
    }
    return $int_filesize;
}
最近在工作中遇到了需要读取系统日志的问题,日志文件很大,大概在1G以上甚至更大,随即研究了读取得方法,直接使用PHP自带的函数就可以解决这几个问题,但是绝对不能使用file和file_get_contents,这两个函数是一次性将文件全部加载进来,如果文件在几十M 还是可以的,但是稍大的文件时不能用的,内存是会溢出的,贴个自己的方法,欢迎大家指教!


关于函数传入的变量$tag的值,根据系统不一样,传入的值也是有区别的:Windows用”\r\n”,linux/unix用”\n”,Mac OS用”\r”。

程序执行的大概流程:先定义读取文件的一些基础变量,然后打开文件,将指针定位在文件的指定位置,并读取指定大小的内容。每读取一次将内容存储在变量中,直到达到读取要求的行数或文件结束。

绝不要假定程序中的一切都将按计划运行。

根据上面的代码,虽然能够得到文件中指定位置、指定大小的数据,但这整个过程只执行了一次,并不能得到所有的数据。其实要得到所有的数据,可以在这个循环的外层再添加判断文件是否结束的循环,但这很浪费系统资源,甚至由于文件过大一直没法读完而导致PHP执行超时。另一种方法就是记录并存储上次读取数据后指针所在的位置,然后再次执行该循环的时候,将指针定位在上次结束的位置,这样就不存在一次循环要把文件从头读到尾的情况。


PHP还有其他方法能够解决,比如system函数,不足之处还望指正!
最近碰到一个比较有趣的问题,就是修改某个文件的某一行字符,不过文件太大,file()直接读取是不可能的,我使用fgets来跳转到指定行,并用fwrite修改某个字符串:</p>
$fp = fopen('d:/file.txt', 'r+');
if ($fp) {
    $i = 1;
    while (!feof($fp)) {
        //修改第二行数据
        if ($i == 2) {
            fseek($fp, 2, SEEK_CUR);
            fwrite($fp, '#');
            break;
        }
        fgets($fp);
        $i++;
    }
    fclose($fp);
}
这里需要注意的是fgets获取到一行后,文件指针指向行尾(也就是下一行开头),所以fwrite操作的是fgets后的下一行开头,至于从该行的第几个字符开始写,可以使用fseek函数来移动文件指针。另外一个需要注意的是,这里fwrite写入是执行替换操作,而不是插入操作,所以指针后面的字符会一个个被替换掉。至于怎么插入我就没研究了。估计很困难。为了效率可能只能写入另外一个临时文件了,不知道有没有其他更好的方法。
另外今天还看到了使用SPL进行操作的方法:
$fp = new SplFileObject('d:/file.txt', 'r+');
//转到第二行, seek方法参数从0开始计数, 经我测试指针指向行尾了, 所以修改的是第三行
$fp->seek(1);
//获取当前行内容(第二行)
$line = $fp->current();
//下面是对第三行的操作
$fp->fseek(2, SEEK_CUR);
$fp->fwrite('#');
 SplFileObject提供的方法比基本的文件操作函数更丰富一些,包括采用key/value方法遍历文件行等。SPL应该是PHP5增加进去的吧,还有其他很多很有用的对象。包括数组、文件目录操作、异常处理、一些基本类型操作等,这些功能还在陆续增加,可以通过继承SPL扩展这些方法让我们处理底层的操作更方便。
<?php
$fp= new SplFileObject('testfile.txt');
$fp->seek(1);
$line = $fp->current();
echo $line;
$fp->seek(881);
$line = $fp->current();
echo $line;
$fp->seek(123456);
$line = $fp->current();
echo $line;
?>

读取到末尾的判断:$fp->eof()
$fp = new SplFileObject('./chat.txt', 'r+');  
$line = 0;  
$totalLine = 0;  
while (!$fp->eof()) {  
$fp->current();  
$totalLine++;  
$fp->next();  
}  
$fp->seek($totalLine);

<?php
$fp = new SplFileObject('./file.txt', 'r+');
//转到第二行, seek方法参数从0开始计数, 经我测试指针指向行尾了, 所以修改的是第三行
$fp->seek(1);
////获取当前行内容(第二行)
$line = $fp->current();
echo $line;
////下面是对第三行的操作
$fp->fseek(2, SEEK_CUR);
$fp->fwrite('#jackxiang');
?>

来源:http://www.cnblogs.com/gaocheng/articles/1778499.html
http://www.php100.com/html/webkaifa/PHP/PHPyingyong/2009/1029/3453.html

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:http://jackxiang.com/post/3495/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2013-9-26 17:20
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]