Bir metin dosyasının satır sayısını verimli bir şekilde saymak. (200 MB +)


90

Komut dosyamın bana ölümcül bir hata verdiğini yeni öğrendim:

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

Bu satır şudur:

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

Yani bence dosyayı hafızaya yüklerken ve satır sayısını saymakta güçlük çekiyor, bunu hafıza sorunları olmadan yapmanın daha verimli bir yolu var mı?

Satır sayısını saymam gereken metin dosyaları 2MB ile 500MB arasında değişiyor. Belki bazen bir Gig.

Herhangi bir yardım için hepinize teşekkürler.

Yanıtlar:


162

Bu, tüm dosyayı belleğe yüklemediği için daha az bellek kullanır:

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

fclose($handle);

echo $linecount;

fgetshafızaya tek bir satır yükler (eğer ikinci argüman $lengthatlanırsa, satırın sonuna gelene kadar akımdan okumaya devam eder, ki bu bizim istediğimiz şeydir). Bellek kullanımı kadar duvar zamanını da önemsiyorsanız, PHP dışında bir şey kullanmak kadar hızlı olması olası değildir.

Bununla ilgili tek tehlike, herhangi bir satırın özellikle uzun olmasıdır (ya satır sonu olmayan 2 GB'lık bir dosya ile karşılaşırsanız?). Bu durumda, onu parçalara ayırıp satır sonu karakterlerini saymanız daha iyi olur:

$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
mükemmel değil: \nbir Windows makinesinde ( PHP_EOL == '\r\n') ayrıştırılan unix tarzı bir dosyanız olabilir ( )
nickf

1
Neden satır okumasını 1 ile sınırlayarak biraz iyileştirmeyelim? Sadece satır sayısını saymak istediğimize göre, neden a yapmayalım fgets($handle, 1);?
Cyril N.

1
@CyrilN. Bu, kurulumunuza bağlıdır. Çoğunlukla satır başına birkaç karakter içeren dosyalarınız varsa, daha hızlı olabilir, çünkü kullanmanız gerekmez substr_count(), ancak çok uzun hatlarınız varsa aramanız gerekir while()ve fgets()çok daha fazlası dezavantaj yaratır. Unutmayın: fgets() satır satır okumaz. Bu sadece aracılığıyla tanımlanmış karakter miktarını okur $lengthve eğer bu bir linebreak içeriyor ne olursa olsun durur $lengthmu kümesine sahip.
mgutt

3
Bu satır sayısından 1 fazla döndürmez mi? while(!feof())Ekstra bir satır okumanıza neden olur, çünkü EOF göstergesi siz dosyanın sonunda okumaya çalışıncaya kadar ayarlanmaz.
Barmar

1
İnanıyorum ilk örnekte @DominicRodger $line = fgets($handle);sadece olabilir fgets($handle);çünkü $linehiçbir zaman kullanılmaz.
Pocketsand

109

Bir fgets()çağrı döngüsü kullanmak iyi bir çözümdür ve yazmak için en basit yöntemdir, ancak:

  1. dosya dahili olarak 8192 baytlık bir arabellek kullanılarak okunsa bile, kodunuzun yine de her satır için bu işlevi çağırması gerekir.

  2. İkili bir dosya okuyorsanız, teknik olarak tek bir satırın mevcut bellekten daha büyük olması mümkündür.

Bu kod, her biri 8kB'lik parçalar halinde bir dosyayı okur ve ardından bu öbek içindeki satırsonu sayısını sayar.

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

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

    fclose($f);

    return $lines;
}

Her bir hattın ortalama uzunluğu en fazla 4kB ise, zaten işlev çağrılarından tasarruf etmeye başlayacaksınız ve bunlar büyük dosyaları işlediğinizde toplanabilir.

Kıyaslama

1GB dosya ile bir test yaptım; sonuçlar burada:

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

Zaman, gerçek zamanlı olarak saniye cinsinden ölçülür, burada gerçek ne anlama geldiğini görün


Arabellek boyutunu 64k gibi bir şeye genişletmenin ne kadar hızlı (?) Olacağını merak ediyorum. Not: Yalnızca php bazı olsaydı kolay bu durumda IO uyumsuz hale getirmek için bir yol
zerkms

@zerkms Sorunuzu yanıtlamak gerekirse, 64kB tamponlarla 1GB'de 0,2 saniye daha hızlı olur :)
Ja͢ck

3
Bu kriterde dikkatli olun, ilk önce hangisini çalıştırdınız? İkincisi, dosyanın zaten disk önbelleğinde olması avantajına sahip olacak ve sonucu büyük ölçüde çarpıtacaktır.
Oliver Charlesworth

7
@OliCharlesworth, ilk koşuyu atlayarak beş koşu üzerinden ortalamalar :)
Ja͢ck

1
Bu cevap harika! Bununla birlikte, IMO, satır sayısına 1 eklemek için son satırda bir karakter olup olmadığını test etmelidir: pastebin.com/yLwZqPR2
caligari

50

Basit Yönelimli Nesne çözümü

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

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

var_dump($file->key());

Güncelleme

Bunu yapmanın başka bir yolu PHP_INT_MAXda SplFileObject::seekyöntemdir.

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

echo $file->key() + 1; 

3
İkinci çözüm harika ve Spl! Teşekkürler.
Daniele Orlando

2
Teşekkür ederim ! Bu gerçekten harika. Ve aramadan daha hızlı wc -l(sanırım çatallaşma nedeniyle), özellikle küçük dosyalarda.
Drasill

1
Mükemmel çözüm!
Dalibor Karlović

2
Şimdiye kadarki en iyi çözüm bu
Valdrinium

1
"Anahtar () + 1" doğru mu? Denedim ve yanlış görünüyor. Sonuncusu dahil her satırda satır sonları olan belirli bir dosya için bu kod bana 3998 verir. Ama üzerinde "wc" yaparsam 3997 alırım. "Vim" kullanırsam 3997L yazıyor (ve eksik olduğunu göstermiyor EOL). Bu yüzden "Güncelle" yanıtının yanlış olduğunu düşünüyorum.
user9645

37

Bunu bir Linux / Unix ana bilgisayarında çalıştırıyorsanız, en kolay çözüm exec()komutu çalıştırmak için kullanmak veya benzer bir çözümdür wc -l $path. Bunun $path"/ yol / / dosya; rm -rf /" gibi bir şey olmadığından emin olmak için önce sterilize ettiğinizden emin olun.


Ben bir pencere makinesindeyim! Öyle olsaydım, bunun en iyi çözüm olacağını düşünüyorum!
Abs

25
@ ghostdog74: Neden, evet, haklısın. Taşınabilir değildir. Bu yüzden önerimin taşınabilir olmadığını açıkça "Eğer bunu bir Linux / Unix ana bilgisayarında çalıştırıyorsanız ..." cümlesinin başına koyarak kabul ettim.
Dave Sherohman

1
Taşınabilir değildir (bazı durumlarda yararlı olsa da), ancak exec (veya shell_exec veya system), PHP yerleşik işlevlerine kıyasla önemli ölçüde daha yavaş olan bir sistem çağrısıdır.
Manz

11
@Manz: Neden, evet, haklısın. Taşınabilir değildir. Bu yüzden önerimin taşınabilir olmadığını açıkça "Eğer bunu bir Linux / Unix ana bilgisayarında çalıştırıyorsanız ..." cümlesinin başına koyarak kabul ettim.
Dave Sherohman

@DaveSherohman Evet, haklısın, üzgünüm. IMHO, bence en önemli konu, bir sistem çağrısında harcanan zaman (özellikle sık kullanmanız gerekiyorsa)
Manz

32

Tüm dosya boyunca döngü yapmayı gerektirmeyen daha hızlı bir yol var

yalnızca * nix sistemlerinde , pencerelerde de benzer bir yol olabilir ...

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

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

"Böyle bir dosya veya dizin yok" seçeneğini gizlemek için 2> / dev / null ekleyin
Tegan Snyder

$ total_lines = intval (exec ("wc -l '$ dosya'")); dosya adlarını boşluklarla işleyecektir.
pgee70

Teşekkürler pgee70 henüz buna rastlamadı ama mantıklı, cevabımı güncelledim
Andy Braham

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

Yukarıdaki yanıt @DaveSherohman tarafından 3 yıl önce gönderilmiş gibi görünüyor
e2-e4

8

PHP 5.5 kullanıyorsanız, bir kullanabilirsiniz jeneratör . Bu olacak DEĞİL olsa 5.5 önce PHP herhangi sürümünde çalışmaz. Php.net'ten:

"Oluşturucular, Yineleyici arabirimini uygulayan bir sınıfın uygulanmasının ek yükü veya karmaşıklığı olmadan basit yineleyicileri uygulamak için kolay bir yol sağlar."

// 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/ finallySizin için PHP otomatik olarak kapanacak dosya, kesinlikle gerekli değildir. Muhtemelen gerçek sayımın iterator_count(getFiles($file)):) kullanılarak yapılabileceğini de belirtmelisiniz
NikiC

7

Bu, Wallace de Souza'nın çözümüne bir eklemedir

Ayrıca sayarken boş satırları da atlar:

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 altındaysanız, şunları yapabilirsiniz:

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

Başka bir işletim sistemi kullanıyorsanız sadece doğru komutu bulmanız gerekir

Saygılarımızla


1
private static function lineCount($file) {
    $linecount = 0;
    $handle = fopen($file, "r");
    while(!feof($handle)){
        if (fgets($handle) !== false) {
                $linecount++;
        }
    }
    fclose($handle);
    return  $linecount;     
}

Yukarıdaki işleve küçük bir düzeltme eklemek istedim ...

'test etme' kelimesini içeren bir dosyam olduğu belirli bir örnekte, işlev sonuç olarak 2'yi döndürdü. bu yüzden fgets yanlış döndürülüp döndürülmediğini bir kontrol eklemem gerekiyor :)

iyi eğlenceler :)


1

Dominic Rodger'ın çözümüne dayanarak, işte kullandığım şey (mevcutsa wc kullanır, aksi takdirde dominic Rodger'ın çözümüne geri döner).

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


1

Aşağıdaki kodlarla satır sayısının sayımı yapılabilir:

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

Birkaç seçeneğiniz var. Birincisi, izin verilen kullanılabilir belleği artırmaktır; bu, dosyanın çok büyük olabileceğini belirttiğinizde işleri yapmanın muhtemelen en iyi yolu değildir. Diğer bir yol, dosyayı satır satır okumak ve bir sayacı artırmak için fgets kullanmaktır ; bu, herhangi bir zamanda yalnızca geçerli satır bellekte olduğu için herhangi bir bellek sorununa neden olmamalıdır.


0

Bu listeye iyi bir katkı olabileceğini düşündüğüm başka bir cevap daha var.

Eğer varsa perlyüklenmiş ve PHP kabuğundan şeyler çalıştırmak edebiliyoruz:

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

Bu, ister Unix'ten ister Windows tarafından oluşturulan dosyalardan olsun, çoğu satır sonunu işlemelidir.

İKİ dezavantaj (en azından):

1) Betiğinizin çalıştığı sisteme bu kadar bağımlı olması harika bir fikir değildir (Perl ve wc'nin mevcut olduğunu varsaymak güvenli olmayabilir)

2) Kaçarken küçük bir hata yaptınız ve makinenizdeki bir mermiye erişim izni verdiniz.

Kodlama hakkında bildiğim (veya bildiğimi düşündüğüm) çoğu şeyde olduğu gibi, bu bilgiyi de başka bir yerden aldım:

John Reeve Makalesi


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
Lütfen OP'ye ve diğer okuyucularınıza orijinal soruya neden ve nasıl cevap verdiğini açıklayan en azından birkaç kelime eklemeyi düşünün.
β.εηοιτ.βε

0

Bu yöntemi sadece bir dosyada kaç satır saymak için kullanıyorum. Bu ayetleri yapmanın dezavantajı nedir diğer cevaplar. İki hatlı çözümümün aksine birçok çizgi görüyorum. Sanırım kimsenin bunu yapmamasının bir sebebi var.

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

Orijinal çözüm şuydu. Ancak dosya () tüm dosyayı belleğe yüklediğinden, bu aynı zamanda orijinal sorundu (Bellek tükenmesi), bu nedenle hayır, bu soru için bir çözüm değil.
Tuim

0

Bir seferde yalnızca bir satırı arabelleğe alan en özlü çapraz platform çözümü.

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

Maalesef, READ_AHEADbayrağı aksi takdirde iterator_countsüresiz olarak bloke etmeliyiz . Aksi takdirde, bu tek satırlık olacaktır.


-1

Sadece satırları saymak için şunu kullanın:

$handle = fopen("file","r");
static $b = 0;
while($a = fgets($handle)) {
    $b++;
}
echo $b;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.