내 스크립트에서 치명적인 오류가 발생한다는 사실을 방금 발견했습니다.
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()
호출 루프를 사용하는 것이 좋은 솔루션이며 작성하기 가장 간단합니다.
-
내부적으로 8192 바이트의 버퍼를 사용하여 파일을 읽더라도 코드는 여전히 각 행에 대해 해당 함수를 호출해야합니다.
-
바이너리 파일을 읽는 경우 기술적으로 한 줄이 사용 가능한 메모리보다 클 수 있습니다.
이 코드는 각각 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_MAX
in SplFileObject::seek
method입니다.
$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;
}
