web-dev-qa-db-ja.com

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

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

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の範囲の行数をカウントする必要があるテキストファイル。たぶんギグ。

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

80
Abs

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

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

fclose($handle);

echo $linecount;

fgets は、1行をメモリにロードします(2番目の引数$lengthを省略すると、行の終わりに到達するまでストリームから読み取りを続けます。これが目的です)。ウォール時間とメモリ使用量を気にする場合、これはまだPHP以外のものを使用するのと同じくらい迅速ではありません。

これに関する唯一の危険は、行が特に長い場合です(改行のない2GBファイルに遭遇した場合はどうなりますか?)。その場合、チャンクで丸lurみし、行末文字をカウントする方が良いでしょう:

$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;
150
Dominic Rodger

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

時間はリアルタイムで秒単位で測定されます。 こちら 本当の意味を参照してください

101
Ja͢ck

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

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

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

var_dump($file->key());

更新

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

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

echo $file->key() + 1; 
41
Wallace Maxters

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

34
Dave Sherohman

ファイル全体をループする必要のない、より高速な方法が見つかりました

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

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

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

PHP 5.5を使用している場合、 generator を使用できます。これは、5.5より前のPHPのどのバージョンでもではなく動作します。 php.netから:

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

// 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
8
Ben Harold

これは Wallace de Souza's ソリューションへの追加です

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

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

Linuxを使用している場合は、次の操作を実行できます。

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

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

よろしく

4
elkolotfi

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

<?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
Santosh Kumar
private static function lineCount($file) {
    $linecount = 0;
    $handle = fopen($file, "r");
    while(!feof($handle)){
        if (fgets($handle) !== false) {
                $linecount++;
        }
    }
    fclose($handle);
    return  $linecount;     
}

上記の機能に少し修正を加えたかった...

「テスト」という単語を含むファイルがある特定の例では、関数は結果として2を返しました。だから私はfgetsがfalseを返したかどうかのチェックを追加する必要がありました:)

楽しんで :)

1
ufk

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

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
Douglas.Sesar

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

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

ドミニクロジャーのソリューションに基づいて、ここに私が使用するものがあります(使用可能な場合は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
ling