परिचय: "घातक त्रुटि" और 2 गीगाबाइट लॉग
\n\nकल्पना करें: आप एक डेवलपर हैं, और आपके पास एक सिस्टम एडमिनिस्ट्रेटर पेन ड्राइव लेकर आता है। उसमें पिछले छह महीने का वेब सर्वर लॉग फ़ाइल है। आकार? मामूली 2.5 गीगाबाइट। "सभी IP पतों का पता लगाना है, जहाँ से ब्रूटफ़ोर्स के प्रयास हुए थे," वह कहता है और कॉफ़ी पीने चला जाता है।
\n\nआप IDE खोलते हैं और यंत्रवत् लिखते हैं:
\n$lines = file('huge_log.txt');\nforeach ($lines as $line) {\n // पार्स कर रहे हैं...\n}\n\nआप "रन" दबाते हैं... और ब्राउज़र में एक सफेद स्क्रीन दिखाई देती है, और एरर लॉग में — Fatal Error: Allowed memory size exhausted। परिचित लग रहा है? यह एक क्लासिक जाल है जिसमें लगभग सभी PHP डेवलपर फँस चुके हैं।
\n\nStack Overflow पर "How to read a large file line by line?" प्रश्न ने सैकड़ों हज़ारों व्यूज़ बिना कारण एकत्र नहीं किए हैं। यह उन कार्यों में से एक है जहाँ "भोला" समाधान सर्वर को मार देता है, और सही दृष्टिकोण प्रोजेक्ट को बचाता है। आज हम सीखेंगे कि PHP में विशाल फ़ाइलों को मेमोरी सीमा को पार किए बिना और उच्च प्रदर्शन बनाए रखते हुए कैसे पढ़ा जाए। चलिए शुरू करते हैं!
\n\nबड़े डेटा के लिए file() और file_get_contents() बुराई क्यों हैं
\n\nआइए तुरंत मुख्य मिथक को दूर करें। फ़ंक्शन file() और file_get_contents() — ये लालची फ़ंक्शन हैं। ये पूरी फ़ाइल को एक साथ RAM में लोड करते हैं। यदि आपकी फ़ाइल 2 GB की है, और php.ini में memory_limit 128 MB (कई होस्टिंग के लिए मानक मान) पर सेट है, तो आपको निश्चित रूप से एक त्रुटि मिलेगी।
इसके अलावा, PHP स्ट्रिंग्स को एन्कोडिंग में संग्रहीत करता है, जो डिस्क पर फ़ाइल आकार की तुलना में मेमोरी खपत को 2-4 गुना बढ़ा सकता है। यानी 2 GB लॉग 6-8 GB RAM "खा" सकते हैं!
\n\nनिष्कर्ष: यदि फ़ाइल का आकार आपके memory_limit के 10-20% से अधिक है, तो file() को भूल जाइए। हमें एक स्ट्रीमिंग दृष्टिकोण की आवश्यकता है।
सही तरीका: fgets() और स्ट्रीमिंग रीडिंग
\n\nसबसे विश्वसनीय और कुशल तरीका fopen() को fgets() के साथ उपयोग करना है। ये फ़ंक्शन फ़ाइल को पूरी तरह से नहीं, बल्कि एक बार में एक पंक्ति पढ़ते हैं। कल्पना करें कि आप एक स्ट्रॉ के माध्यम से जूस पी रहे हैं — आप एक घूंट में पूरा गिलास नहीं पीते, बल्कि थोड़ा-थोड़ा करके पीते हैं। स्ट्रीमिंग रीडिंग ठीक उसी तरह काम करती है।
यहाँ एक बुनियादी, लेकिन पहले से काम करने वाला उदाहरण है:
\n\n<?php\n$handle = fopen('huge_log.txt', 'r');\nif ($handle) {\n while (($line = fgets($handle)) !== false) {\n // एक पंक्ति प्रोसेस करें\n echo $line;\n }\n fclose($handle);\n}\n?>\n\n\nयहाँ क्या हो रहा है? fopen() फ़ाइल खोलता है और एक पॉइंटर (resource) लौटाता है। fgets() हर बार न्यू लाइन कैरेक्टर (\\n) तक एक पंक्ति पढ़ता है। मेमोरी केवल वर्तमान पंक्ति के लिए आवंटित होती है, पूरी फ़ाइल के लिए नहीं। प्रोसेसिंग के बाद, पंक्ति गार्बेज कलेक्टर द्वारा नष्ट (या ओवरराइट) हो जाती है।
प्रदर्शन के महत्वपूर्ण बिंदु
\n- \n
- बफ़र आकार:
fgets()डिफ़ॉल्ट रूप से एक बार में 8192 बाइट तक पढ़ता है। यदि पंक्तियाँ लंबी हैं, तो फ़ंक्शन कई पुनरावृत्तियाँ करेगा। अधिकांश लॉग के लिए यह पर्याप्त है। \n - false की जाँच: हमेशा सख्त तुलना
!== falseका उपयोग करें। क्यों? क्योंकि एक पंक्ति खाली ("") हो सकती है या इसमें "0" हो सकता है, औरwhile ($line = fgets($handle))समय से पहले रुक सकता है। \n - फ़ाइल बंद करना: हमेशा
fclose()को कॉल करें। हाँ, PHP स्क्रिप्ट समाप्त होने पर यह स्वयं करेगा, लेकिन यदि आप किसी लूप या बैकग्राउंड स्क्रिप्ट में फ़ाइल प्रोसेस कर रहे हैं, तो बंद न किए गए डिस्क्रिप्टर संसाधन रिसाव हैं। \n
सुपर-चार्ज: पुन: उपयोग के लिए जनरेटर (yield)
\n\nमैन्युअल fopen/fclose प्रबंधन अच्छा है, लेकिन यदि आपको प्रोजेक्ट के विभिन्न भागों में विभिन्न फ़ाइलों को पढ़ने की आवश्यकता है, तो कोड दोहराया जाने लगेगा। यहाँ जनरेटर मदद करते हैं — PHP की सबसे सुरुचिपूर्ण विशेषताओं में से एक।
जनरेटर आपको एक फ़ंक्शन बनाने की अनुमति देता है जो