কোনও পাঠ্য ফাইলের লাইনের সংখ্যা দক্ষতার সাথে গণনা করা। (২০০ এমবি +)


90

আমি সবেমাত্র জানতে পেরেছি যে আমার স্ক্রিপ্টটি আমাকে মারাত্মক ত্রুটি দেয়:

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 অবধি রেখার সংখ্যা গণনা করা দরকার। কখনও কখনও একটি গিগ হতে পারে।

সকল সাহায্যের জন্য ধন্যবাদ।

উত্তর:


162

এটি কম স্মৃতি ব্যবহার করবে, কারণ এটি পুরো ফাইলটিকে মেমরিতে লোড করে না:

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

fclose($handle);

echo $linecount;

fgetsএকটি একক লাইনকে মেমরিতে লোড করে (দ্বিতীয় যুক্তি $lengthবাদ দিলে এটি লাইনের শেষ প্রান্তে পৌঁছানো অবধি স্ট্রিম থেকে পড়া চালিয়ে যাবে, যা আমরা চাই) যদি আপনি প্রাচীরের সময় পাশাপাশি মেমরির ব্যবহারের বিষয়ে চিন্তা করেন তবে এটি পিএইচপি ব্যতীত অন্য কিছু ব্যবহার করার মতো দ্রুত হওয়ার সম্ভাবনা এখনও কম।

এর সাথে একমাত্র বিপদটি হ'ল যদি কোনও লাইন বিশেষত দীর্ঘ হয় (আপনি যদি লাইন ব্রেক ব্যতীত 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;

4
নিখুঁত নয়: আপনার \nউইন্ডোজ মেশিনে একটি ইউনিক্স-স্টাইল ফাইল ( ) পার্স করা হতে পারে ( PHP_EOL == '\r\n')
নিকফ

4
1 এ লাইন রিডিং সীমাবদ্ধ রেখে কিছুটা উন্নতি করবেন না কেন? যেহেতু আমরা কেবল লাইনের সংখ্যা গণনা করতে চাই, কেন একটি করবে না fgets($handle, 1);?
সিরিল এন।

4
@ সাইরিলএন এটি আপনার সেটআপের উপর নির্ভর করে। যদি আপনার বেশিরভাগ ফাইল থাকে যা প্রতি লাইনে কেবল কিছু অক্ষর ধারণ করে তবে এটি দ্রুততর হতে পারে কারণ আপনার ব্যবহারের প্রয়োজন নেই substr_count(), তবে আপনি যদি খুব দীর্ঘ লাইনে থাকেন তবে আপনাকে কল করতে হবে while()এবং fgets()আরও অনেক অসুবিধায় পরিণত হচ্ছে। ভুলে যাবেন না: fgets() লাইনে লাইন পড়েন না। এটি কেবলমাত্র আপনি যে পরিমাণ অক্ষরের মাধ্যমে সংজ্ঞায়িত করেছেন তা পড়তে পারে $lengthএবং এতে যদি একটি লাইনব্রেক থাকে তবে যা $lengthসেট করা হয়েছে তা বন্ধ হয়ে যায় ।
মিগডুট

4
এই রিটার্নটি কি লাইনের সংখ্যার চেয়ে 1 টি বেশি হবে না? while(!feof())আপনাকে একটি অতিরিক্ত লাইন পড়তে বাধ্য করবে, কারণ আপনি ফাইলের শেষে পড়ার চেষ্টা না করা পর্যন্ত ইওএফ সূচক সেট করা নেই।
বার্মার

4
@ ডোমিনিকরোজার প্রথম উদাহরণ হিসাবে আমি বিশ্বাস করি $line = fgets($handle);যে এটি কখনই ব্যবহৃত হয় না fgets($handle);কারণ হতে পারে $line
পকেটস্যান্ড

109

fgets()কলগুলির একটি লুপ ব্যবহার করা সূক্ষ্ম সমাধান এবং লেখার জন্য সবচেয়ে সহজ for

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

যদি প্রতিটি লাইনের গড় দৈর্ঘ্য সর্বোচ্চ 4 কেবি হয় তবে আপনি ইতিমধ্যে ফাংশন কলগুলিতে সঞ্চয় শুরু করবেন এবং আপনি বড় ফাইলগুলি প্রক্রিয়া করার সময় এগুলি যুক্ত করতে পারবেন can

মাপকাঠি

আমি 1 জিবি ফাইল দিয়ে একটি পরীক্ষা চালিয়েছি; ফলাফল এখানে:

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

সময়টি সেকেন্ডে রিয়েল টাইমে মাপা হয়, আসল অর্থ কী তা এখানে দেখুন


আপনি যদি বাফারের আকারটি 64 কে এর মতো কিছুতে প্রসারিত করেন তবে এটি কত দ্রুত (?) কৌতূহলজনক। পিএস: যদি কেবল পিএইচপি এই ক্ষেত্রে
আইওকে অবিচ্ছিন্ন

@ জারকিমস আপনার প্রশ্নের উত্তর দিতে, 64 কেবি বাফার দিয়ে এটি 1 গিগাবাইটে 0.2 সেকেন্ড দ্রুত হয়ে যায় :)
জ্যাক

4
এই বেঞ্চমার্কটি সম্পর্কে সাবধানতা অবলম্বন করুন, আপনি কোনটি আগে চালিত করেছেন? দ্বিতীয়টিটিতে ফাইলটি ইতিমধ্যে ডিস্ক ক্যাশে থাকা সুবিধাগুলি পাবে, ফলাফলটি ব্যাপকভাবে স্কাইং করে।
অলিভার চার্লসওয়ার্থ

7
@ অলিচর্লেসওয়ার্থ তারা পাঁচ রানেরও বেশি গড়ে প্রথম রান এড়িয়ে গেল :)
জ্যাক

4
এই উত্তর দুর্দান্ত! যাইহোক, আইএমও, লাইন কাউন্টে 1 যুক্ত করার জন্য শেষ লাইনে কিছু অক্ষর রয়েছে কিনা তা পরীক্ষা করতে হবে: পেস্টবিন.
com

50

সরল ওরিয়েন্টেড অবজেক্ট সলিউশন

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

4
দ্বিতীয় সমাধানটি দুর্দান্ত এবং স্প্ল ব্যবহার করে! ধন্যবাদ
ড্যানিয়েল অরল্যান্ডো

4
ধন্যবাদ ! এটি আসলেই দুর্দান্ত great এবং কল করার চেয়ে দ্রুত wc -l(বিশেষত ছোট ফাইলগুলিতে) আমি মনে করি forking এর কারণে।
ড্রাসিল

4
দুর্দান্ত সমাধান!
ডালিবোর কার্লোভিć

4
এটি এখন পর্যন্ত সেরা সমাধান
ভালড্রিনিয়াম

4
"কী () + 1" ঠিক আছে? আমি চেষ্টা করেছিলাম এবং ভুল বলে মনে হচ্ছে। সর্বশেষ সহ প্রতিটি লাইনে লাইন শেষ সহ একটি প্রদত্ত ফাইলের জন্য, এই কোডটি আমাকে 3998 দেয় But তবে আমি যদি "wc" করি তবে আমি 3997 পেয়ে যাই I ইওএল)। সুতরাং আমি "আপডেট" উত্তর ভুল বলে মনে করি।
user9645

37

আপনি যদি একটি লিনাক্স / ইউনিক্স হোস্ট এই চালিয়ে থাকেন, সহজ সমাধান ব্যবহার করতে হবে exec()বা অনুরূপ কমান্ড চালানোর জন্য wc -l $path। শুধু নিশ্চিত করুন যে আপনি sanitized করেছি $path"; RM -rf / / পথ / থেকে / ফাইল" প্রথম নিশ্চিত যে এটা ভালো কিছু নয় যাবে।


আমি উইন্ডোজ মেশিনে আছি! আমি যদি হতাম, আমি মনে করি যে এটিই সেরা সমাধান হতে পারে!
Abs

25
@ ঘোস্টডোগ 74৪: কেন, হ্যাঁ, আপনি ঠিক বলেছেন। এটি অ-বহনযোগ্য। এ কারণেই আমি "আপনি যদি এটি লিনাক্স / ইউনিক্স হোস্টে চালাচ্ছেন ..." শর্তটি দিয়ে আমার পরামর্শটির অ-বহনযোগ্যতার স্পষ্টরূপে স্বীকার করেছেন।
ডেভ শেরোহমান

4
নন পোর্টেবল (কিছু পরিস্থিতিতে কার্যকর হলেও) তবে এক্সিকিউটিভ (বা শেল_এক্সেক বা সিস্টেম) একটি সিস্টেম কল, যা পিএইচপি অন্তর্নির্মিত ফাংশনের তুলনায় যথেষ্ট ধীর।
মান্জ

11
@ মঞ্জ: কেন, হ্যাঁ, আপনি ঠিক বলেছেন। এটি অ-বহনযোগ্য। এ কারণেই আমি "আপনি যদি এটি লিনাক্স / ইউনিক্স হোস্টে চালাচ্ছেন ..." শর্তটি দিয়ে আমার পরামর্শটির অ-বহনযোগ্যতার স্পষ্টরূপে স্বীকার করেছেন।
ডেভ শেরোহমান

@ ডেভ শেরোহমান হ্যাঁ, আপনি ঠিক বলেছেন, দুঃখিত। আইএমএইচও, আমি মনে করি যে সর্বাধিক গুরুত্বপূর্ণ সমস্যাটি হ'ল সিস্টেম কলে সময় ব্যয় করা (বিশেষত যদি আপনাকে ঘন ঘন ব্যবহারের প্রয়োজন হয়)
মানজ

32

আমি আরও একটি দ্রুত উপায় পেয়েছি যে পুরো ফাইলটি লুপিং প্রয়োজন হয় না

শুধুমাত্র * নিক্স সিস্টেমে উইন্ডোতেও একই ধরণের উপায় থাকতে পারে ...

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

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

"এরকম কোনও ফাইল বা ডিরেক্টরি নেই" দমন করতে 2> / দেব / নাল যুক্ত করুন
টেগান স্নাইডার

$ টোটাল_লাইনস = ইনটুল (এক্সিকিউটিউট ("ডাব্লুসি-ল '' ফাইল '")); স্পেস সহ ফাইলের নামগুলি পরিচালনা করবে।
pgee70

ধন্যবাদ pgee70 এখনও এটি পেরিয়ে আসেনি তবে তা বোধগম্য হয়েছে, আমি আমার উত্তর আপডেট করেছি
অ্যান্ডি ব্রাহাম

6
exec('wc -l '.escapeshellarg($file).' 2>/dev/null')
ঝেং কাই

@DaveSherohman এর উত্তর দেখে মনে হচ্ছে উপরের এইটির 3 বছর আগে পোস্ট করেছেন
e2-e4

8

আপনি যদি পিএইচপি 5.5 ব্যবহার করেন তবে আপনি একটি জেনারেটর ব্যবহার করতে পারেন । এটি 5.5 এর আগে পিএইচপি-র কোনও সংস্করণে কাজ করবে না । পিএইচপি.net থেকে:

"জেনারেটরগুলি আইট্রেটার ইন্টারফেস প্রয়োগ করে এমন শ্রেণি প্রয়োগের ক্ষেত্রে ওভারহেড বা জটিলতা ছাড়াই সহজ পুনরাবৃত্তি কার্যকর করার সহজ উপায় সরবরাহ করে।"

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

4
try/ finallyকঠোরভাবে প্রয়োজনীয়, পিএইচপি স্বয়ংক্রিয়ভাবে বন্ধ হবে আপনার জন্য ফাইল নয়। আপনার সম্ভবত এটিও উল্লেখ করা উচিত যে প্রকৃত iterator_count(getFiles($file))
গণনাটি

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

আপনি যদি লিনাক্সের আওতায় থাকেন তবে আপনি কেবল তা করতে পারেন:

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

আপনি যদি অন্য কোনও ওএস ব্যবহার করছেন তবে আপনাকে সঠিক কমান্ডটি সন্ধান করতে হবে

শ্রদ্ধা


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 টি ফাংশন ফিরে আসে returned fgets মিথ্যা প্রমাণিত হয়েছে বা না যদি আমার একটি চেক যোগ করার প্রয়োজন ছিল :)

আনন্দ কর :)


1

ডমিনিক রজারের সমাধানের উপর ভিত্তি করে, আমি এখানে যা ব্যবহার করি তা এখানে রয়েছে (এটি যদি পাওয়া যায় তবে ডাব্লুসিটি ব্যবহার করে, অন্যথায় রজারের দ্রষ্টব্যতে ফলব্যাক)।

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

লাইন সংখ্যা গণনা নিম্নলিখিত কোড দ্বারা করা যেতে পারে:

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

আপনার কাছে বেশ কয়েকটি বিকল্প রয়েছে। প্রথমটি হ'ল অনুমোদিত উপলভ্য মেমরিটি বাড়ানো, যা আপনার ফাইলটি খুব বড় আকারের পেতে পারে বলে উল্লেখ করা জিনিসগুলি করার সম্ভবত সেরা উপায় নয়। অন্য উপায়টি হ'ল লাইন দ্বারা ফাইল লাইনটি পড়তে এবং একটি কাউন্টার বাড়িয়ে দেওয়ার জন্য fgets ব্যবহার করা , যার ফলে কোনও মেমরির সমস্যা হওয়া উচিত নয় কারণ কেবলমাত্র বর্তমান লাইনটি কোনও এক সময়ে মেমরিতে থাকে।


0

আরও একটি উত্তর আছে যা আমি ভেবেছিলাম এই তালিকায় একটি ভাল সংযোজন হতে পারে।

যদি আপনি perlপিএইচপি-তে শেল থেকে জিনিসগুলি ইনস্টল করে থাকেন এবং সক্ষম হন:

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

এটি ইউনিক্স বা উইন্ডোজ থেকে তৈরি ফাইলগুলি সর্বাধিক লাইন ব্রেকগুলি পরিচালনা করবে।

TWO ডাউনসাইডস (কমপক্ষে):

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

4
ওপিকে ব্যাখ্যা করার জন্য এবং আপনার আরও পাঠকদের কমপক্ষে কিছু শব্দ যুক্ত করার বিষয়ে দয়া করে বিবেচনা করুন কারণ এটি কেন এবং কীভাবে মূল প্রশ্নের উত্তর দেয়।
εηοιτ.εηοιτ.βε

0

কোনও ফাইলের মধ্যে কতগুলি লাইন বিশুদ্ধভাবে গণনা করার জন্য আমি এই পদ্ধতিটি ব্যবহার করি। এই আয়াতগুলি করার অসুবিধাগুলি অন্য উত্তরগুলি। আমার দুটি লাইন সমাধানের বিপরীতে আমি অনেকগুলি লাইন দেখছি। আমি অনুমান করছি যে এরকম কেউ কারণ না করে।

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

মূল সমাধানটি এটি ছিল। ফাইল যেহেতু ফাইল () পুরো ফাইলটিকে মেমরিতে লোড করে এটি এটিও মূল সমস্যা ছিল (মেমরি ক্লান্তি) তাই না, এটি প্রশ্নের সমাধান নয়।
Tuim

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;
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.