[php] 텍스트 파일의 줄 수를 효율적으로 계산합니다. (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까지의 줄 수를 계산하는 데 필요한 텍스트 파일입니다. 가끔 공연 일 수도 있습니다.

도움을 주셔서 감사합니다.



답변

전체 파일을 메모리에로드하지 않기 때문에 메모리를 덜 사용합니다.

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

fclose($handle);

echo $linecount;

fgets한 줄을 메모리에로드합니다 (두 번째 인수 $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;


답변

fgets()호출 루프를 사용하는 것이 좋은 솔루션이며 작성하기 가장 간단합니다.

  1. 내부적으로 8192 바이트의 버퍼를 사용하여 파일을 읽더라도 코드는 여전히 각 행에 대해 해당 함수를 호출해야합니다.

  2. 바이너리 파일을 읽는 경우 기술적으로 한 줄이 사용 가능한 메모리보다 클 수 있습니다.

이 코드는 각각 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   |
+------------+-------------+------------------+---------+

시간은 실시간으로 초 단위로 측정됩니다. 여기에서 실제 의미를 확인 하세요.


답변

단순 지향 객체 솔루션

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

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

var_dump($file->key());

최신 정보

이것을 만드는 또 다른 방법은 PHP_INT_MAXin SplFileObject::seekmethod입니다.

$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에서도 비슷한 방법이있을 수 있습니다.

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

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


답변

PHP 5.5를 사용하는 경우 생성기를 사용할 수 있습니다 . 이것은 5.5 이전의 PHP 버전에서는 작동 하지 않습니다 . php.net에서 :

“Generator는 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


답변

이것은 Wallace de Souza의 솔루션에 추가되었습니다.

또한 계산하는 동안 빈 줄을 건너 뜁니다.

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