テキストファイルの行数を効率的にカウントします。(200mb +)


88

スクリプトで致命的なエラーが発生することがわかりました。

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

その行はこれです:

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

したがって、ファイルをメモリにロードして行数を数えるのが難しいと思いますが、メモリの問題なしにこれを行うためのより効率的な方法はありますか?

行数を数える必要があるテキストファイルは、2MBから500MBの範囲です。たぶんギグ。

助けてくれてありがとう。

回答:


161

これにより、ファイル全体がメモリにロードされないため、使用するメモリが少なくなります。

$file="largefile.txt";
$linecount = 0;
$handle = fopen($file, "r");
while(!feof($handle)){
  $line = fgets($handle);
  $linecount++;
}

fclose($handle);

echo $linecount;

fgets1行をメモリにロードします(2番目の引数$lengthを省略すると、行の終わりに達するまでストリームから読み取りを続けます。これが必要です)。実時間とメモリ使用量を気にする場合、これはPHP以外のものを使用するほど速くはありません。

これに伴う唯一の危険は、行が特に長い場合です(改行のない2GBのファイルに遭遇した場合はどうなりますか?)。その場合は、チャンクにまとめて、行末の文字を数える方がよいでしょう。

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

5
完璧ではありません:UNIXスタイルのファイル(\n)をWindowsマシン(PHP_EOL == '\r\n')で解析する可能性があります
nickf 2010年

1
行の読み取りを1に制限して、少し改善してみませんか?行数だけを数えたいので、どうしてfgets($handle, 1);
シリルN.

1
@CyrilN。これはセットアップによって異なります。1行あたりの文字数が少ないファイルがほとんどの場合はsubstr_count()、を使用する必要がないため高速になる可能性がありますが、非常に長い行がある場合は呼び出す必要があり、while()さらにfgets()多くの問題が発生します。忘れないでください: fgets()行ごとに読みません。それだけであなたを介して定義された文字の量を読み取っ$lengthあれば、それは改行が含まれていることは何でも停止し$lengthていセットを持っています。
mgutt 2015年

3
これは行数より1多い数を返しませんか?while(!feof())EOFインジケータは、ファイルの最後で読み取ろうとするまで設定されないため、余分な行を読み取ることになります。
バーマー2015

1
最初の例の@DominicRodgerは、使用されていないためである$line = fgets($handle);可能性があります。fgets($handle);$line
Pocketsand 2016年

107

fgets()ただし、呼び出しのループを使用することは優れたソリューションであり、最も簡単に記述できます。

  1. 内部的には、ファイルは8192バイトのバッファーを使用して読み取られますが、コードは各行に対してその関数を呼び出す必要があります。

  2. バイナリファイルを読み取っている場合、技術的には1行が使用可能なメモリよりも大きくなる可能性があります。

このコードは、それぞれ8kBのチャンクでファイルを読み取り、そのチャンク内の改行の数をカウントします。

function getLines($file)
{
    $f = fopen($file, 'rb');
    $lines = 0;

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

    fclose($f);

    return $lines;
}

各行の平均の長さが最大で4kBの場合、関数呼び出しの節約をすでに開始しており、大きなファイルを処理するときにそれらが合計される可能性があります。

基準

1GBのファイルでテストを実行しました。結果は次のとおりです。

             +-------------+------------------+---------+
             | This answer | Dominic's answer | wc -l   |
+------------+-------------+------------------+---------+
| Lines      | 3550388     | 3550389          | 3550388 |
+------------+-------------+------------------+---------+
| Runtime    | 1.055       | 4.297            | 0.587   |
+------------+-------------+------------------+---------+

時間はリアルタイムで秒単位で測定されます。実際の意味はこちらをご覧ください


バッファサイズを64kのようなものに拡張すると、どれだけ速くなるか(?)が気になります。PS:この場合、
PHP

@zerkmsは、それが0.2秒速く:) 1ギガバイトになり64kBのバッファを、あなたの質問に答えるために
ジャック

3
このベンチマークに注意してください。最初に実行したのはどれですか。2つ目は、ファイルがすでにディスクキャッシュにあるという利点があり、結果が大幅に歪められます。
オリバーチャールズワース2014

6
@OliCharlesworth彼ら5回以上している平均値は、最初の実行:)スキップ
ジャック・

1
この答えは素晴らしいです!:行数に1を追加するための最後の行でいくつかの文字がある場合しかし、IMO、それはテストする必要がありますpastebin.com/yLwZqPR2
カリガリ

48

シンプルな指向オブジェクトソリューション

$file = new \SplFileObject('file.extension');

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

var_dump($file->key());

更新

これを行う別の方法は、PHP_INT_MAXinSplFileObject::seekメソッドを使用することです。

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

echo $file->key() + 1; 

3
2番目のソリューションは素晴らしく、Splを使用しています!ありがとう。
ダニエレオーランド

2
ありがとうございました !これは確かに素晴らしいです。そしてwc -l、特に小さなファイルでは、呼び出すよりも高速です(私が推測するフォークのため)。
ドラシル2016

私はその解決策がそれほど役立つとは思いませんでした!
Wallace Maxters 2016年

2
これは断然最良の解決策です
Valdrinium 2017年

1
「key()+ 1」は正しいですか?私はそれを試しましたが、間違っているようです。最後を含むすべての行で行末がある特定のファイルの場合、このコードは3998を返します。しかし、「wc」を実行すると3997になります。「vim」を使用すると3997Lと表示されます(欠落していることを示すものではありません)。 EOL)。ですから、「更新」の答えは間違っていると思います。
user9645

37

Linux / Unixホストでこれを実行している場合、最も簡単な解決策はexec()、コマンドを実行するために使用するか、同様の方法wc -l $pathです。$path最初にサニタイズして、「/ path / to / file; rm -rf /」のようなものではないことを確認してください。


私はウィンドウズマシンにいます!もしそうなら、それが最善の解決策だと思います!
Abs 2010年

24
@ ghostdog74:なぜ、そうです、あなたは正しいです。ポータブルではありません。そのため、「Linux / Unixホストでこれを実行している場合...」という句を前に付けて、提案の移植性がないことを明示的に認めました。
Dave Sherohman 2010年

1
移植性はありませんが(状況によっては便利ですが)、exec(またはshell_execまたはsystem)はシステムコールであり、PHPの組み込み関数に比べてかなり低速です。
manz 2012年

11
@マンツ:なぜ、はい、あなたは正しいです。ポータブルではありません。そのため、「Linux / Unixホストでこれを実行している場合...」という句を前に付けて、提案の移植性がないことを明示的に認めました。
Dave Sherohman 2012年

@DaveSherohmanはい、その通りです、ごめんなさい。私見、最も重要な問題はシステムコールに時間がかかることだと思います(特に頻繁に使用する必要がある場合)
Manz 2012年

32

ファイル全体をループする必要がない、より速い方法があります。

* nixシステムのみ、 Windowsでも同様の方法がある可能性があります...

$file = '/path/to/your.file';

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

2> / dev / nullを追加して、「そのようなファイルまたはディレクトリはありません」を抑制します
Tegan Snyder

$ total_lines = intval(exec( "wc -l '$ file'")); スペースを含むファイル名を処理します。
pgee70 2013

おかげでpgee70はまだそれに出くわしませんでしたが、理にかなっています、私は私の答えを更新しました
Andy Braham

6
exec('wc -l '.escapeshellarg($file).' 2>/dev/null')
Zheng Kai

上記の@DaveSherohmanによる回答は、これの3年前に投稿されたようです
e2-

8

PHP 5.5を使用している場合は、ジェネレーターを使用できます。ただし、これは5.5より前のどのバージョンのPHPでも機能しません。php.netから:

「ジェネレーターは、イテレーターインターフェースを実装するクラスを実装するオーバーヘッドや複雑さなしに、単純なイテレーターを実装する簡単な方法を提供します。」

// 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

5
try/は、finallyあなたのためのPHPが自動的に閉じ、ファイル、厳密には必要ではありません。実際のカウントは次を使用して実行できることにも言及する必要がありますiterator_count(getFiles($file)):)
NikiC 2013年

7

これはウォレスデソウザのソリューションへの追加です

また、カウント中に空の行をスキップします。

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

6

Linuxを使用している場合は、次のようにするだけです。

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

別のOSを使用している場合は、適切なコマンドを見つける必要があります

よろしく


1
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を返したかどうかのチェックを追加する必要がありました:)

楽しんで :)


1

行数のカウントは、次のコードで実行できます。

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

0

いくつかのオプションがあります。1つ目は、使用可能なメモリを増やすことです。これは、ファイルが非常に大きくなる可能性があることを考えると、おそらく最善の方法ではありません。もう1つの方法は、fgetsを使用してファイルを1行ずつ読み取り、カウンターをインクリメントすることです。これにより、現在の行のみが一度にメモリに存在するため、メモリの問題はまったく発生しません。


0

このリストに追加するのが良いと私が思った別の答えがあります。

perlインストールしてPHPのシェルから実行できる場合:

$lines = exec('perl -pe \'s/\r\n|\n|\r/\n/g\' ' . escapeshellarg('largetextfile.txt') . ' | wc -l');

これは、UnixまたはWindowsで作成されたファイルからかどうかにかかわらず、ほとんどの改行を処理する必要があります。

2つの欠点(少なくとも):

1)スクリプトを実行しているシステムにそれほど依存させるのは良い考えではありません(Perlとwcが利用可能であると想定するのは安全ではないかもしれません)

2)エスケープの小さな間違いで、マシンのシェルへのアクセスを引き渡しました。

コーディングについて私が知っている(または知っていると思う)ほとんどのことと同様に、私は他の場所からこの情報を入手しました:

ジョンリーブの記事


0
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>";
}

5
OPに説明する単語を少なくともいくつか追加することを検討してください。さらに読者に、元の質問に回答する理由と方法について回答してください。
β.εηοιτ.βε

0

ドミニックロジャーのソリューションに基づいて、これが私が使用するものです(可能な場合はwcを使用し、そうでない場合はドミニックロジャーのソリューションへのフォールバックを使用します)。

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


0

私はこの方法を使用して、ファイル内の行数を純粋にカウントします。これを行うことの欠点は、他の答えとは異なります。2行のソリューションとは対照的に、多くの行が表示されます。誰もこれをしない理由があると思います。

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

元々の解決策はこれでした。しかし、file()はファイル全体をメモリにロードするため、これは元の問題(メモリの枯渇)でもあったため、いいえ、これは質問の解決策ではありません。
Tuim 2017年

0

一度に1行しかバッファリングしない最も簡潔なクロスプラットフォームソリューション。

$file = new \SplFileObject(__FILE__);
$file->setFlags($file::READ_AHEAD);
$lines = iterator_count($file);

残念ながら、READ_AHEADフラグを設定する必要がありiterator_countます。そうしないと、無期限にブロックされます。そうでなければ、これはワンライナーになります。


-1

行を数えるだけの場合は、次を使用します。

$handle = fopen("file","r");
static $b = 0;
while($a = fgets($handle)) {
    $b++;
}
echo $b;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.