关于php:有效地计算文本文件的行数

Efficiently counting the number of lines of a text file. (200mb+)

我刚发现我的脚本给了我一个致命的错误:

1
Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 440 bytes) in C:\process_txt.php on line 109

这条线是:

1
$lines = count(file($path)) - 1;

因此,我认为将文件加载到内存和计算行数有困难,有没有更有效的方法可以做到这一点而不出现内存问题?

我需要计算从2MB到500MB范围内的行数的文本文件。有时可能是演出。

谢谢大家的帮助。


这将占用较少的内存,因为它不会将整个文件加载到内存中:

1
2
3
4
5
6
7
8
9
10
11
$file="largefile.txt";
$linecount = 0;
$handle = fopen($file,"r");
while(!feof($handle)){
  $line = fgets($handle);
  $linecount++;
}

fclose($handle);

echo $linecount;

fgets将一行加载到内存中(如果省略了第二个参数$length,它将一直从流中读取,直到到达行的末尾,这是我们想要的)。如果您关心墙的时间和内存的使用,这仍然不可能像使用PHP以外的东西那么快。

唯一的危险是如果任何行特别长(如果遇到没有换行符的2GB文件怎么办?)。在这种情况下,你最好把它分成块,然后计算行尾字符:

1
2
3
4
5
6
7
8
9
10
11
$file="largefile.txt";
$linecount = 0;
$handle = fopen($file,"r");
while(!feof($handle)){
  $line = fgets($handle, 4096);
  $linecount = $linecount + substr_count($line, PHP_EOL);
}

fclose($handle);

echo $linecount;


使用一个循环的fgets()调用是很好的解决方案,也是最容易编写的方法,但是:

  • 即使在内部使用8192字节的缓冲区读取文件,代码仍然必须为每行调用该函数。

  • 从技术上讲,如果您正在读取一个二进制文件,那么一行可能比可用内存大。

  • 这段代码读取一个8KB的文件块,然后计算该块中的换行数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function getLines($file)
    {
        $f = fopen($file, 'rb');
        $lines = 0;

        while (!feof($f)) {
            $lines += substr_count(fread($f, 8192),"
    "
    );
        }

        fclose($f);

        return $lines;
    }

    如果每行的平均长度至多为4KB,那么就已经开始保存函数调用,并且在处理大文件时,这些调用可以相加。

    基准

    我用一个1GB文件运行了一个测试;结果如下:

    1
    2
    3
    4
    5
    6
    7
                 +-------------+------------------+---------+
                 | This answer | Dominic's answer | wc -l   |
    +------------+-------------+------------------+---------+
    | Lines      | 3550388     | 3550389          | 3550388 |
    +------------+-------------+------------------+---------+
    | Runtime    | 1.055       | 4.297            | 0.587   |
    +------------+-------------+------------------+---------+

    时间是以秒为单位实时测量的,看看这里真正的意思是什么


    面向对象的简单解决方案

    1
    2
    3
    4
    5
    $file = new \SplFileObject('file.extension');

    while($file->valid()) $file->fgets();

    var_dump($file->key());

    更新

    另一种方法是使用SplFileObject::seek方法中的PHP_INT_MAX

    1
    2
    3
    4
    $file = new \SplFileObject('file.extension', 'r');
    $file->seek(PHP_INT_MAX);

    echo $file->key() + 1;


    如果您在Linux/Unix主机上运行这个命令,最简单的解决方案是使用exec()或类似的方法来运行命令wc -l $path。先确认你已经对$path进行了消毒,以确保它不是类似于"/path/to/file;rm-rf/"。


    我发现有一种更快的方法,不需要在整个文件中循环

    只有在*nix系统上,Windows上可能有类似的方法…

    1
    2
    3
    4
    $file = '/path/to/your.file';

    //Get number of lines
    $totalLines = intval(exec("wc -l '$file'"));


    如果您使用的是PHP5.5,那么可以使用生成器。但这在5.5之前的任何版本的PHP中都不起作用。来自PHP.NET:

    "生成器提供了一种简单的方法来实现简单的迭代器,而无需实现实现迭代器接口的类的开销或复杂性。"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // This function implements a generator to load individual lines of a large file
    function getLines($file) {
        $f = fopen($file, 'r');

        // read each line of the file without loading the whole file to memory
        while ($line = fgets($f)) {
            yield $line;
        }
    }

    // Since generators implement simple iterators, I can quickly count the number
    // of lines using the iterator_count() function.
    $file = '/path/to/file.txt';
    $lineCount = iterator_count(getLines($file)); // the number of lines in the file


    这是华莱士·德索扎解决方案的补充

    它还跳过空行数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function getLines($file)
    {
        $file = new \SplFileObject($file, 'r');
        $file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY |
    SplFileObject::DROP_NEW_LINE);
        $file->seek(PHP_INT_MAX);

        return $file->key() + 1;
    }

    如果您使用的是Linux,那么只需执行以下操作:

    1
    number_of_lines = intval(trim(shell_exec("wc -l".$file_name." | awk '{print $1}'")));

    如果使用其他操作系统,只需找到正确的命令

    当做


    可以通过以下代码计算行数:

    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    $fp= fopen("myfile.txt","r");
    $count=0;
    while($line = fgetss($fp)) // fgetss() is used to get a line from a file ignoring html tags
    $count++;
    echo"Total number of lines  are".$count;
    fclose($fp);
    ?>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private static function lineCount($file) {
        $linecount = 0;
        $handle = fopen($file,"r");
        while(!feof($handle)){
            if (fgets($handle) !== false) {
                    $linecount++;
            }
        }
        fclose($handle);
        return  $linecount;    
    }

    我想在上面的函数中添加一个小补丁…

    在一个特定的示例中,我有一个包含单词"testing"的文件,结果函数返回了2。所以我需要添加一个检查fgets是否返回false:)

    玩得开心:


    你有几个选择。首先是增加允许的可用内存,这可能不是最好的方法,因为您声明文件可能会变得非常大。另一种方法是使用fgets逐行读取文件并增加一个计数器,这不会导致任何内存问题,因为在任何时候只有当前行在内存中。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public function quickAndDirtyLineCounter()
    {
        echo"<table>";
        $folders = ['C:\wamp\www\qa\abcfolder\',
        ];
        foreach ($folders as $folder) {
            $files = scandir($folder);
            foreach ($files as $file) {
                if($file == '
    .' || $file == '..' || !file_exists($folder.'\'.$file)){
                    continue;
                }
                    $handle = fopen($folder.'
    /'.$file,"r");
                    $linecount = 0;
                    while(!feof($handle)){
                        if(is_bool($handle)){break;}
                        $line = fgets($handle);
                        $linecount++;
                      }
                    fclose($handle);
                    echo"<tr><td>" . $folder ."</td><td>" . $file ."</td><td>" . $linecount ."</td></tr>";
                }
            }
            echo"</table>";
    }


    基于多米尼克·罗杰的解决方案,这里是我使用的(如果可用,它使用wc,否则返回到DominicRodger的解决方案)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class FileTool
    {

        public static function getNbLines($file)
        {
            $linecount = 0;

            $m = exec('which wc');
            if ('' !== $m) {
                $cmd = 'wc -l <"' . str_replace('"', '\"', $file) . '"';
                $n = exec($cmd);
                return (int)$n + 1;
            }


            $handle = fopen($file,"r");
            while (!feof($handle)) {
                $line = fgets($handle);
                $linecount++;
            }
            fclose($handle);
            return $linecount;
        }
    }

    https://github.com/lingtalfi/bat/blob/master/filetool.php


    我认为还有另一个答案可能是这个列表的一个很好的补充。

    如果您安装了perl,并且能够在php中从shell运行东西:

    1
    2
    3
    4
    5
    6
    $lines = exec('perl -pe \'s/

    |
    |
    /
    /g\' '
    . escapeshellarg('largetextfile.txt') . ' | wc -l');

    这应该处理大多数换行符,不管是来自Unix还是Windows创建的文件。

    两个缺点(至少):

    1)让您的脚本如此依赖于运行它的系统不是一个好主意(假定Perl和WC可用可能不安全)。

    2)只是一个小小的逃逸错误,你已经把机器上的外壳交给了你。

    正如我知道(或认为我知道)的大多数关于编码的事情一样,我从其他地方得到了这些信息:

    约翰·里夫文章


    为了计算行数,请使用:

    1
    2
    3
    4
    5
    6
    $handle = fopen("file","r");
    static $b = 0;
    while($a = fgets($handle)) {
        $b++;
    }
    echo $b;

    我使用这个方法来计算一个文件中有多少行。这样做的缺点是什么?与其他答案相比。我看到很多行与我的两行解决方案相反。我想没人这么做是有原因的。

    1
    2
    $lines = count(file('your.file'));
    echo $lines;