किसी पाठ फ़ाइल की पंक्तियों की संख्या को कुशलता से गिनना। (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;

इसलिए मुझे लगता है कि फ़ाइल को मेमरी में लोड करने और लाइनों की संख्या की गिनती करने में कठिनाई हो रही है, क्या कोई अधिक कुशल तरीका है कि मैं स्मृति मुद्दों के बिना ऐसा कर सकता हूं?

पाठ फ़ाइलें जिन्हें मुझे 2 एमबी से 500 एमबी तक की लाइनों की संख्या की गणना करने की आवश्यकता है। शायद एक टमटम कभी कभी।

किसी भी मदद के लिए धन्यवाद।

जवाबों:


161

यह कम मेमोरी का उपयोग करेगा, क्योंकि यह पूरी फाइल को मेमोरी में लोड नहीं करता है:

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

fclose($handle);

echo $linecount;

fgets स्मृति में एक पंक्ति लोड करता है (यदि दूसरा तर्क $length छोड़ दिया जाता है तो यह धारा से तब तक पढ़ता रहेगा जब तक यह पंक्ति के अंत तक नहीं पहुंच जाता, जो हम चाहते हैं)। यह अभी भी PHP के अलावा किसी अन्य चीज़ का उपयोग करने के रूप में जल्दी होने की संभावना नहीं है, अगर आप दीवार के समय के साथ-साथ मेमोरी उपयोग के बारे में परवाह करते हैं।

इसके साथ एकमात्र खतरा यह है कि यदि कोई लाइनें विशेष रूप से लंबी हैं (क्या होगा यदि आप बिना लाइन ब्रेक के 2 जीबी फाइल का सामना करते हैं?)। जिस स्थिति में आप इसे घुटनों में मोड़ना और अंत वर्णों की गिनती करना बेहतर समझते हैं:

$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
बिल्कुल सही नहीं: आपके पास एक यूनिक्स-शैली की फाइल ( \n) एक विंडोज़ मशीन पर पार्स की जा सकती है ( PHP_EOL == '\r\n')
nicf

1
1 को पढ़ने के लिए लाइन को सीमित करके थोड़ा सुधार क्यों नहीं किया जाता है? चूँकि हम केवल रेखाओं की संख्या गिनना चाहते हैं, तो क्यों नहीं fgets($handle, 1);?
सिरिल एन।

1
@CyrilN। यह आपके सेटअप पर निर्भर करता है। यदि आप ज्यादातर ऐसी फाइलें रखते हैं जिनमें प्रति पंक्ति केवल कुछ वर्ण होते हैं तो यह तेज हो सकती है क्योंकि आपको उपयोग करने की आवश्यकता नहीं है substr_count(), लेकिन यदि आपके पास बहुत लंबी लाइनें हैं तो आपको कॉल करने की आवश्यकता होती है while()और fgets()बहुत अधिक नुकसान होता है। मत भूलो: fgets() लाइन से लाइन नहीं पढ़ता है। यह केवल आपके द्वारा परिभाषित वर्णों की मात्रा को पढ़ता है $lengthऔर यदि इसमें एक लाइनब्रेक होता है $lengthतो जो कुछ भी सेट किया गया है वह रुक जाता है ।
mgutt

3
क्या यह वापसी लाइनों की संख्या से 1 अधिक नहीं होगी? while(!feof())जब आप फ़ाइल के अंत में पढ़ने की कोशिश करेंगे, तो ईओएफ संकेतक सेट नहीं होने के कारण आपको एक अतिरिक्त लाइन पढ़ने का कारण होगा।
बरमार

1
@DominicRodger के पहले उदाहरण में मेरा मानना ​​है कि $line = fgets($handle);सिर्फ fgets($handle);इसलिए हो सकता है क्योंकि $lineइसका इस्तेमाल कभी नहीं किया गया।
पॉकेटैंड

107

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

समय को वास्तविक समय में मापा जाता है, यहां देखें कि वास्तविक अर्थ क्या है


जिज्ञासु कितना तेज (?) होगा यदि आप बफर आकार को 64k जैसी किसी चीज तक बढ़ाते हैं। पुनश्च: अगर केवल php के पास इस मामले में IO को अतुल्यकालिक बनाने के लिए कुछ आसान तरीका है
zerkms

@zerkms आपके प्रश्न का उत्तर देने के लिए, 64kB बफ़र्स के साथ यह 1GB पर 0.2 सेकंड तेज हो जाता है :)
Ja͢ck

3
इस बेंचमार्क से सावधान रहें, जो आपने पहले चलाया था? दूसरे को फ़ाइल का लाभ पहले से ही डिस्क कैश में होगा, परिणाम को बड़े पैमाने पर तिरछा करना होगा।
ओलिवर चार्ल्सवर्थ

6
@OliCharlesworth वे पांच रन से अधिक के औसत हैं, पहला रन लंघन :)
Ja 28ck

1
यह जवाब बहुत अच्छा है! हालाँकि, IMO, यह परीक्षण करना चाहिए कि लाइन की गिनती में 1 जोड़ने के लिए अंतिम पंक्ति में कुछ वर्ण है: pastebin.com/yLwZqPR2
कैलिगारी

48

सरल उन्मुख वस्तु समाधान

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

3
दूसरा उपाय बढ़िया है और स्प्ल का उपयोग करता है! धन्यवाद।
डेनियल ऑरलैंडो

2
धन्यवाद ! यह वास्तव में महान है। और कॉल करने की तुलना में तेज़ है wc -l(क्योंकि मुझे लगता है कि फोर्किंग), खासकर छोटी फाइलों पर।
Drasill

मैंने नहीं सोचा था कि समाधान इतना उपयोगी होगा!
वालेस मैक्सर्स

2
यह अब तक का सबसे अच्छा उपाय है
Valdrinium

1
क्या "कुंजी () + 1" सही है? मैंने इसे आजमाया और गलत लगता है। अंतिम सहित हर लाइन पर लाइन अंत के साथ दी गई फ़ाइल के लिए, यह कोड मुझे 3998 देता है। लेकिन अगर मैं इस पर "wc" करता हूं, तो मुझे 3997 मिलता है। यदि मैं "vim" का उपयोग करता हूं, तो यह 3997L कहता है (और गायब होने का संकेत नहीं देता है। EOL)। इसलिए मुझे लगता है कि "अपडेट" उत्तर गलत है।
user9645

37

यदि आप इसे लिनक्स / यूनिक्स होस्ट पर चला रहे हैं, exec()तो कमांड को चलाने के लिए सबसे आसान समाधान उपयोग या समान होगा wc -l $path। बस सुनिश्चित करें कि आपने $pathपहले यह सुनिश्चित किया है कि यह "/ पथ / से / फ़ाइल; rm -rf /" जैसा कुछ नहीं है।


मैं एक विंडोज़ मशीन पर हूँ! अगर मैं था, मुझे लगता है कि सबसे अच्छा समाधान होगा!
पेट

23
@ ghostdog74: क्यों, हाँ, आप सही कह रहे हैं। यह गैर-पोर्टेबल है। इसीलिए मैंने स्पष्ट रूप से "यदि आप इसे लिनक्स / यूनिक्स होस्ट पर चला रहे हैं ..." खंड के साथ पूर्व निर्धारित करके मेरे सुझाव को गैर-पोर्टेबिलिटी स्वीकार किया है।
डेव शेरोमैन

1
गैर पोर्टेबल (हालांकि कुछ स्थितियों में उपयोगी), लेकिन निष्पादन (या शेल_सेक्स या सिस्टम) एक सिस्टम कॉल है, जो PHP के अंतर्निहित कार्यों की तुलना में काफी धीमी है।
मंज़

10
@Manz: क्यों, हाँ, तुम सही हो। यह गैर-पोर्टेबल है। इसीलिए मैंने स्पष्ट रूप से "यदि आप इसे लिनक्स / यूनिक्स होस्ट पर चला रहे हैं ..." खंड के साथ पूर्व निर्धारित करके मेरे सुझाव को गैर-पोर्टेबिलिटी स्वीकार किया है।
डेव शेरोहमान

@DaveSherohman हाँ, आप सही हैं, क्षमा करें। IMHO, मुझे लगता है कि सबसे महत्वपूर्ण मुद्दा सिस्टम कॉल में समय लगता है (विशेषकर यदि आपको बार-बार उपयोग करने की आवश्यकता है)
मंज़ूर

32

एक तेज़ तरीका है जो मैंने पाया कि पूरी फ़ाइल के माध्यम से लूपिंग की आवश्यकता नहीं है

केवल * निक्स सिस्टम पर , विंडोज़ पर एक समान तरीका हो सकता है ...

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

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

"> ऐसी कोई फ़ाइल या निर्देशिका" को दबाने के लिए 2> / dev / null जोड़ें
Tegan Snyder

$ total_lines = intval (निष्पादन ("wc -l '$ फ़ाइल'")); रिक्त स्थान के साथ फ़ाइल नाम संभाल लेंगे।
pgee70

धन्यवाद pgee70 उस पर अभी तक नहीं आया था लेकिन समझ में आता है, मैंने अपना जवाब अपडेट किया
एंडी ब्रहम

6
exec('wc -l '.escapeshellarg($file).' 2>/dev/null')
झेंग काई

@DaveSherohman द्वारा उत्तर की तरह लग रहा है इस पोस्ट से 3 साल पहले एक
e2-e4

8

यदि आप PHP 5.5 का उपयोग कर रहे हैं, तो आप एक जनरेटर का उपयोग कर सकते हैं । यह नहीं होगा 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

5
try/ finallyअत्यंत आवश्यक होता, पीएचपी स्वचालित रूप से बंद हो जाएगा आप के लिए फ़ाइल नहीं है। आपको शायद यह भी उल्लेख करना चाहिए कि वास्तविक गिनती का उपयोग iterator_count(getFiles($file)):) किया जा सकता है :
NikiC

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

मैं ऊपर के फंक्शन में थोड़ा फिक्स जोड़ना चाहता था ...

एक विशिष्ट उदाहरण में जहां मेरे पास एक फाइल थी जिसमें 'परीक्षण' शब्द था, जिसके परिणामस्वरूप फ़ंक्शन 2 वापस आ गया। इसलिए मुझे एक जांच जोड़ने की जरूरत है कि क्या गलत झूठे लौट आए या नहीं :)

मज़े करो :)


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

आपके पास कई विकल्प हैं। सबसे पहले स्वीकार्य स्मृति को बढ़ाना है, जो कि दी गई चीजों को करने का सबसे अच्छा तरीका नहीं है, जो कि आप कहते हैं कि फ़ाइल बहुत बड़ी हो सकती है। दूसरा तरीका है कि लाइन द्वारा फाइल लाइन को पढ़ने और काउंटर को बढ़ाने के लिए फिजिट्स का उपयोग किया जाए, जिससे किसी भी तरह की मेमोरी इश्यू नहीं हो।


0

एक और जवाब है कि मुझे लगा कि इस सूची में एक अच्छा जोड़ हो सकता है।

यदि आपने perlइंस्टॉल किया है और PHP में शेल से चीजों को चलाने में सक्षम हैं:

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

यह यूनिक्स या विंडोज द्वारा बनाई गई फ़ाइलों से अधिकांश पंक्ति विराम संभालना चाहिए।

दो डाउनसाइड्स (कम से कम):

1) यह एक महान विचार नहीं है कि आपकी स्क्रिप्ट इतनी प्रणाली पर निर्भर करती है कि यह चल रहा है (पर्ल और डब्ल्यूसी उपलब्ध होने के लिए यह सुरक्षित नहीं हो सकता है)

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
कृपया ओपी को समझाते हुए कम से कम कुछ शब्दों को जोड़ने पर विचार करें और आगे के पाठकों को जवाब दें कि यह मूल प्रश्न का उत्तर क्यों और कैसे देता है।
β.βοιτ.βε

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

मैं इस पद्धति का उपयोग विशुद्ध रूप से गिनती के लिए करता हूं कि किसी फाइल में कितनी लाइनें हैं। ऐसा करने का नकारात्मक पक्ष यह है कि अन्य उत्तर छंद हैं। मैं अपने दो लाइन समाधान के विपरीत कई लाइनें देख रहा हूं। मुझे लगता है कि कोई भी ऐसा नहीं करता है।

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

मूल समाधान यह था। लेकिन चूंकि फ़ाइल () संपूर्ण फ़ाइल को मेमोरी में लोड करती है इसलिए यह मूल मुद्दा (मेमोरी थकावट) भी था, इसलिए यह सवाल का हल नहीं है।
तैमूर

0

सबसे रसीला क्रॉस-प्लेटफ़ॉर्म समाधान जो एक समय में केवल एक लाइन को बफ़र करता है।

$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;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.