रूबी मैजिक के इस संस्करण में, हम रूबी में स्ट्रीमिंग फाइलों के बारे में जानेंगे कि कैसे IO
क्लास फाइलों को पूरी तरह से मेमोरी में लोड किए बिना पढ़ने को संभालता है, और यह कैसे रीड बाइट्स को बफर करके प्रति लाइन फाइलों को पढ़ता है। चलो सही में गोता लगाएँ!
“स्लर्पिंग” और स्ट्रीमिंग फ़ाइलें
रूबी का File.read
विधि किसी फ़ाइल को पढ़ती है और उसकी पूरी सामग्री लौटाती है।
irb> content = File.read("log/production.log")
=> "I, [2018-06-27T16:45:02.843719 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Started GET \"/articles\" for 127.0.0.1 at 2018-06-27 16:45:02 +0200\nI, [2018-06-27T16:45:02.846719 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Processing by ArticlesController#index as HTML\nI, [2018-06-27T16:45:02.848212 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Rendering articles/index.html.erb within layouts/application\nD, [2018-06-27T16:45:02.850020 #9098] DEBUG -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Article Load (0.3ms) SELECT \"articles\".* FROM \"articles\"\nI, [2018-06-27T16:45:02.850901 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Rendered articles/index.html.erb within layouts/application (1.7ms)\nI, [2018-06-27T16:45:02.851633 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Completed 200 OK in 5ms (Views: 3.4ms | ActiveRecord: 0.3ms)\n"
आंतरिक रूप से, यह फ़ाइल को खोलता है, इसकी सामग्री को पढ़ता है, फ़ाइल को बंद करता है, और सामग्री को एक स्ट्रिंग के रूप में वापस करता है। फ़ाइल की सामग्री को एक बार में "स्लर्पिंग" करके, इसे तब तक मेमोरी में रखा जाता है जब तक कि इसे रूबी के कचरा संग्रहकर्ता द्वारा साफ नहीं किया जाता है।
एक उदाहरण के रूप में, मान लें कि हम एक फ़ाइल के सभी वर्णों को अपरकेस करना चाहते हैं और इसे दूसरी फ़ाइल में लिखना चाहते हैं। File.read
. का उपयोग करना , हम सामग्री प्राप्त कर सकते हैं, कॉल कर सकते हैं String#upcase
परिणामी स्ट्रिंग पर, और अपरकेस वाली स्ट्रिंग को File.write
. पर पास करें ।
irb> upcased = File.read("log/production.log").upcase
=> "I, [2018-06-27T16:45:02.843719 #9098] INFO -- : [86A5D18C-19DD-4CBF-9D7A-461C79E98C22] STARTED GET \"/ARTICLES\" FOR 127.0.0.1 AT 2018-06-27 16:45:02 +0200\nI, [2018-06-27T16:45:02.846719 #9098] INFO -- : [86A5D18C-19DD-4CBF-9D7A-461C79E98C22] PROCESSING BY ARTICLESCONTROLLER#INDEX AS HTML\nI, [2018-06-27T16:45:02.848212 #9098] INFO -- : [86A5D18C-19DD-4CBF-9D7A-461C79E98C22] RENDERING ARTICLES/INDEX.HTML.ERB WITHIN LAYOUTS/APPLICATION\nD, [2018-06-27T16:45:02.850020 #9098] DEBUG -- : [86A5D18C-19DD-4CBF-9D7A-461C79E98C22] ARTICLE LOAD (0.3MS) SELECT \"ARTICLES\".* FROM \"ARTICLES\"\nI, [2018-06-27T16:45:02.850901 #9098] INFO -- : [86A5D18C-19DD-4CBF-9D7A-461C79E98C22] RENDERED ARTICLES/INDEX.HTML.ERB WITHIN LAYOUTS/APPLICATION (1.7MS)\nI, [2018-06-27T16:45:02.851633 #9098] INFO -- : [86A5D18C-19DD-4CBF-9D7A-461C79E98C22] COMPLETED 200 OK IN 5MS (VIEWS: 3.4MS | ACTIVERECORD: 0.3MS)\n"
irb> File.write("log/upcased.log", upcased)
=> 896
जबकि यह छोटी फ़ाइलों के लिए काम करता है, बड़ी फ़ाइलों के साथ काम करते समय पूरी फ़ाइल को मेमोरी में पढ़ना समस्याग्रस्त हो सकता है। उदाहरण के लिए, 14-गीगाबाइट लॉग फ़ाइल को पार्स करते समय, पूरी फ़ाइल को एक बार में पढ़ना एक महंगा ऑपरेशन होगा। फ़ाइल की सामग्री को मेमोरी में रखा जाता है, इसलिए ऐप की मेमोरी फ़ुटप्रिंट काफी बढ़ जाती है। इससे अंततः मेमोरी की अदला-बदली हो सकती है और OS ऐप की प्रक्रिया को समाप्त कर सकता है।
सौभाग्य से, रूबी File.foreach
. का उपयोग करके लाइन दर पंक्ति फ़ाइलों को पढ़ने की अनुमति देती है . फ़ाइल की पूरी सामग्री को एक बार में पढ़ने के बजाय, यह प्रत्येक पंक्ति के लिए एक पारित ब्लॉक निष्पादित करेगा।
इसका परिणाम गणना योग्य है, इसलिए यह या तो प्रत्येक पंक्ति के लिए एक ब्लॉक उत्पन्न करता है, या यदि कोई ब्लॉक पारित नहीं होता है तो एक एन्यूमरेटर ऑब्जेक्ट देता है। यह बड़ी फ़ाइलों को उनकी सभी सामग्री को एक साथ मेमोरी में लोड किए बिना पढ़ने में सक्षम बनाता है।
irb> File.foreach("log/production.log") { |line| p line }
"I, [2018-06-27T16:45:02.843719 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Started GET \"/articles\" for 127.0.0.1 at 2018-06-27 16:45:02 +0200\n"
"I, [2018-06-27T16:45:02.846719 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Processing by ArticlesController#index as HTML\n"
"I, [2018-06-27T16:45:02.848212 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Rendering articles/index.html.erb within layouts/application\n"
"D, [2018-06-27T16:45:02.850020 #9098] DEBUG -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Article Load (0.3ms) SELECT \"articles\".* FROM \"articles\"\n"
"I, [2018-06-27T16:45:02.850901 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Rendered articles/index.html.erb within layouts/application (1.7ms)\n"
"I, [2018-06-27T16:45:02.851633 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Completed 200 OK in 5ms (Views: 3.4ms | ActiveRecord: 0.3ms)\n"
एक पूरी फाइल को अपरकेस करने के लिए, हम इनपुट फाइल लाइन से लाइन से पढ़ते हैं, इसे अपरकेस करते हैं, और इसे आउटपुट फाइल में जोड़ते हैं।
irb> File.open("upcased.log", "a") do |output|
irb* File.foreach("production.log") { |line| output.write(line.upcase) }
irb> end
=> nil
तो, पूरी फ़ाइल को पहले पढ़े बिना लाइन द्वारा फ़ाइल लाइन को पढ़ना कैसे काम करता है? इसे समझने के लिए, हमें फ़ाइलों को पढ़ने के आसपास की कुछ परतों को हटाना होगा। आइए रूबी के IO
पर करीब से नज़र डालें कक्षा।
I/O और Ruby's IO
कक्षा
भले ही File.read
और File.foreach
मौजूद है, File
. के लिए दस्तावेज़ीकरण कक्षा उन्हें सूचीबद्ध नहीं करती है। वास्तव में, आपको फ़ाइल पढ़ने या लिखने का कोई भी तरीका File
. में नहीं मिलेगा वर्ग दस्तावेज़ीकरण, क्योंकि वे मूल IO
. से विरासत में मिले हैं कक्षा।
I/O
एक I/O डिवाइस एक उपकरण है जो कंप्यूटर से या उससे डेटा स्थानांतरित करता है, उदाहरण के लिए कीबोर्ड, डिस्प्ले और हार्ड ड्राइव। यह इनपुट/आउटपुट करता है , या I/O , डेटा की धाराओं को पढ़कर या उत्पन्न करके।
हार्ड ड्राइव से फ़ाइलें पढ़ना और लिखना सबसे आम I/O है जिसका आप सामना करेंगे। अन्य प्रकार के I/O में सॉकेट संचार, आपके टर्मिनल पर लॉगिंग आउटपुट और आपके कीबोर्ड से इनपुट शामिल हैं।
IO
रूबी में कक्षा फाइलों को पढ़ने और लिखने जैसे सभी इनपुट और आउटपुट को संभालती है। क्योंकि फ़ाइलें पढ़ना किसी अन्य I/O स्ट्रीम से पढ़ने से अलग नहीं है, File
क्लास सीधे IO.read
. जैसी विधियों को इनहेरिट करती है और IO.foreach
।
irb> IO.foreach("log/production.log") { |line| p line }
"I, [2018-06-27T16:45:02.843719 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Started GET \"/articles\" for 127.0.0.1 at 2018-06-27 16:45:02 +0200\n"
"I, [2018-06-27T16:45:02.846719 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Processing by ArticlesController#index as HTML\n"
"I, [2018-06-27T16:45:02.848212 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Rendering articles/index.html.erb within layouts/application\n"
"D, [2018-06-27T16:45:02.850020 #9098] DEBUG -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Article Load (0.3ms) SELECT \"articles\".* FROM \"articles\"\n"
"I, [2018-06-27T16:45:02.850901 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Rendered articles/index.html.erb within layouts/application (1.7ms)\n"
"I, [2018-06-27T16:45:02.851633 #9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Completed 200 OK in 5ms (Views: 3.4ms | ActiveRecord: 0.3ms)\n"
File.foreach
IO.foreach
. के बराबर है , इसलिए IO
क्लास संस्करण का उपयोग वही परिणाम प्राप्त करने के लिए किया जा सकता है जो हमने पहले किया था।
कर्नेल के माध्यम से I/O स्ट्रीम पढ़ना
आंतरिक रूप से, रूबी का IO
कक्षा 'पढ़ने और लिखने की क्षमता कर्नेल सिस्टम कॉल के आस-पास के सार पर आधारित होती है। ऑपरेटिंग सिस्टम का कर्नेल I/O डिवाइस से पढ़ने और लिखने का ध्यान रखता है।
फ़ाइलें खोलना
IO.sysopen
फ़ाइल को फ़ाइल तालिका में फ़ाइल का संदर्भ देने के लिए कर्नेल से पूछकर और प्रक्रिया फ़ाइल डिस्क्रिप्टर तालिका में फ़ाइल डिस्क्रिप्टर बनाकर फ़ाइल खोलता है।
फाइल डिस्क्रिप्टर और फाइल टेबल
फ़ाइल खोलना एक फ़ाइल डिस्क्रिप्टर देता है - I/O संसाधन तक पहुंचने के लिए उपयोग किया जाने वाला एक पूर्णांक।
फ़ाइल डिस्क्रिप्टर को मेमोरी में रखने के लिए प्रत्येक प्रक्रिया की अपनी फ़ाइल डिस्क्रिप्टर तालिका होती है, और प्रत्येक डिस्क्रिप्टर सिस्टम-वाइड फ़ाइल तालिका में एक प्रविष्टि की ओर इशारा करता है। .
I/O संसाधन से पढ़ने या लिखने के लिए, प्रक्रिया फ़ाइल डिस्क्रिप्टर को सिस्टम कॉल के माध्यम से कर्नेल को पास करती है। कर्नेल तब प्रक्रिया की ओर से फ़ाइल तक पहुँचता है, क्योंकि प्रक्रियाओं की फ़ाइल तालिका तक पहुँच नहीं होती है।
फ़ाइलें खोलना नहीं उनकी सामग्री को स्मृति में रखें, लेकिन फ़ाइल डिस्क्रिप्टर तालिका भर सकती है, इसलिए फ़ाइलों को खोलने के बाद उन्हें हमेशा बंद करना एक अच्छा अभ्यास है। File.open
wrap को लपेटने के तरीके जैसे File.read
इसे स्वचालित रूप से करें, साथ ही ब्लॉक लेने वाले भी करें।
इस उदाहरण में, हम IO.sysopen
. पर कॉल करके एक कदम और आगे बढ़ेंगे विधि सीधे। फ़ाइल नाम पास करके, विधि एक फ़ाइल डिस्क्रिप्टर बनाती है जिसका उपयोग हम बाद में खुली फ़ाइल को संदर्भित करने के लिए कर सकते हैं।
irb> IO.sysopen("log/production.log")
=> 9
एक IO
बनाने के लिए रूबी को पढ़ने और लिखने के लिए, हम फाइल डिस्क्रिप्टर को IO.new
. पर पास करते हैं
irb> file_descriptor = IO.sysopen("log/production.log")
=> 9
irb> io = IO.new(file_descriptor)
=> #<IO:fd 9>
I/O स्ट्रीम को बंद करने और फ़ाइल तालिका से फ़ाइल के संदर्भ को हटाने के लिए, हम IO#close
कहते हैं IO
. पर उदाहरण।
irb> io.close
=> nil
बाइट्स पढ़ना और कर्सर ले जाना
IO#sysread
IO
. से कई बाइट पढ़ता है वस्तु।
irb> io.sysread(64)
=> " [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Started GET \"/articles\" "
यह उदाहरण IO
. का उपयोग करता है उदाहरण हमने पहले फ़ाइल डिस्क्रिप्टर पूर्णांक को IO.new
. पर पास करके बनाया था . यह IO#sysread
. पर कॉल करके फ़ाइल के पहले 64 बाइट्स को पढ़ता और लौटाता है इसके तर्क के रूप में 64 के साथ।
irb> io.sysread(64)
=> "for 127.0.0.1 at 2018-06-27 16:45:02 +0200\nI, [2018-06-27T16:45:"
पहली बार जब हमने फ़ाइल से बाइट्स का अनुरोध किया, तो कर्सर अपने आप स्थानांतरित हो गया, इसलिए IO#sysread
को कॉल करना उसी उदाहरण पर फिर से फ़ाइल के अगले 64 बाइट्स का उत्पादन होगा।
कर्सर को मूव करना
IO.sysseek
मैन्युअल रूप से कर्सर को फ़ाइल में किसी स्थान पर ले जाता है।
irb> io.sysseek(32)
=> 32
irb> io.sysread(64)
=> "9098] INFO -- : [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Started "
irb> io.sysseek(0)
=> 0
irb> io.sysread(64)
=> " [86a5d18c-19dd-4cbf-9d7a-461c79e98c22] Started GET \"/articles\" "
इस उदाहरण में, हम 32 की स्थिति में जाते हैं, फिर IO#sysread
. का उपयोग करके 64 बाइट्स पढ़ते हैं . IO.sysseek
. पर कॉल करके फिर से 0 के साथ, हम फ़ाइल की शुरुआत में वापस कूदते हैं, जिससे हमें पहले 64 बाइट्स को फिर से पढ़ने की अनुमति मिलती है।
फ़ाइलों को पंक्ति दर पंक्ति पढ़ना
अब, हम जानते हैं कि कैसे IO
कक्षा की सुविधा विधियां आईओ स्ट्रीम खोलती हैं, उनसे बाइट पढ़ती हैं और वे कर्सर की स्थिति को कैसे स्थानांतरित करती हैं।
IO.foreach
. जैसे तरीके और IO#gets
बाइट्स की संख्या के बजाय लाइन से लाइन लाइन का अनुरोध कर सकते हैं। अगली नई पंक्ति खोजने और उस स्थिति तक सभी बाइट्स लेने के लिए आगे देखने का कोई प्रदर्शन करने वाला तरीका नहीं है, इसलिए रूबी को फ़ाइल की सामग्री को विभाजित करने की देखभाल करने की आवश्यकता है।
class MyIO
def initialize(filename)
fd = IO.sysopen(filename)
@io = IO.new(fd)
end
def each(&block)
line = ""
while (c = @io.sysread(1)) != $/
line << c
end
block.call(line)
each(&block)
rescue EOFError
@io.close
end
end
इस उदाहरण कार्यान्वयन में, #each
विधि IO#sysread
. का उपयोग करके फ़ाइल से बाइट लेती है एक बार में, जब तक कि बाइट $/
न हो जाए , एक नई लाइन का संकेत। जब उसे एक नई लाइन मिलती है, तो वह बाइट लेना बंद कर देती है और उस लाइन के साथ पास हुए ब्लॉक को कॉल करती है।
यह समाधान काम करता है लेकिन अक्षम है क्योंकि यह IO.sysread
. कहता है फ़ाइल में प्रत्येक बाइट के लिए।
बफ़रिंग फ़ाइल सामग्री
रूबी फ़ाइल की सामग्री के आंतरिक बफर को रखकर यह कैसे करती है इसके बारे में बेहतर है। फ़ाइल को एक बार में एक बाइट पढ़ने के बजाय, यह एक बार में 512 बाइट्स लेता है और जांचता है कि लौटा बाइट्स में कोई नई लाइन है या नहीं। यदि वहाँ हैं, तो यह भाग को न्यूलाइन से पहले लौटाता है और बाकी को मेमोरी में बफर के रूप में रखता है। यदि बफ़र में एक नई पंक्ति शामिल नहीं है, तो यह 512 बाइट्स तब तक प्राप्त करता है जब तक कि उसे एक नहीं मिल जाता।
class MyIO
def initialize(filename)
fd = IO.sysopen(filename)
@io = IO.new(fd)
@buffer = ""
end
def each(&block)
@buffer << @io.sysread(512) until @buffer.include?($/)
line, @buffer = @buffer.split($/, 2)
block.call(line)
each(&block)
rescue EOFError
@io.close
end
end
इस उदाहरण में, #each
विधि एक आंतरिक @buffer
में बाइट जोड़ती है जब तक @buffer
. तक 512 बाइट्स के टुकड़ों में परिवर्तनशील वेरिएबल में एक नई लाइन शामिल है। जब ऐसा होता है, तो यह बफर को पहली नई लाइन से विभाजित करता है। पहला भाग line
है , और दूसरा भाग नया बफर है।
पारित ब्लॉक को फिर लाइन और शेष @buffer
. के साथ बुलाया जाता है अगले लूप में उपयोग के लिए रखा जाता है।
फ़ाइल की सामग्री को बफ़र करके, फ़ाइल को तार्किक भागों में विभाजित करते समय I/O कॉल की संख्या कम हो जाती है।
स्ट्रीमिंग फ़ाइलें
संक्षेप में, स्ट्रीमिंग फ़ाइलें ऑपरेटिंग सिस्टम के कर्नेल को एक फ़ाइल खोलने के लिए कहकर काम करती हैं, फिर इसमें से बाइट्स को थोड़ा-थोड़ा करके पढ़ें। रूबी में प्रति पंक्ति एक फ़ाइल पढ़ते समय, डेटा एक बार में 512 बाइट्स फ़ाइल से लिया जाता है और उसके बाद "लाइनों" में विभाजित किया जाता है।
यह रूबी में I/O और स्ट्रीमिंग फ़ाइलों के हमारे अवलोकन को समाप्त करता है। हमें यह जानना अच्छा लगेगा कि आपने इस लेख के बारे में क्या सोचा, या यदि आपके कोई प्रश्न हैं। हम हमेशा जांच करने और समझाने के लिए विषयों की तलाश में रहते हैं, इसलिए यदि रूबी में कुछ जादुई है जिसके बारे में आप पढ़ना चाहते हैं, तो बेझिझक हमें अभी @AppSignal पर बताएं!