रूबी मैजिक के इस संस्करण में, हम रूबी में फाइल्स को स्ट्रीम करना सीखेंगे, कैसे 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 का उपयोग करके फाइल्स को लाइन दर लाइन पढ़ने की अनुमति देती है। फाइल के पूरे कंटेंट को एक बार में पढ़ने के बजाय, यह पास किए गए ब्लॉक को प्रत्येक लाइन के लिए निष्पादित करेगा।
इसका परिणाम एन्युमरेबल है, इसलिए यह या तो प्रत्येक लाइन के लिए ब्लॉक को यील्ड करता है, या अगर कोई ब्लॉक पास नहीं किया गया तो Enumerator ऑब्जेक्ट लौटाता है। यह बड़े फाइल्स को उनकी पूरी कंटेंट को एक साथ मेमोरी में लोड किए बिना पढ़ने में सक्षम बनाता है।
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 और रूबी की 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 को लपेटने वाले मेथड्स जैसे 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 इंस्टेंस पर IO#close कॉल करते हैं।
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 क्लास के सुविधा मेथड्स I/O स्ट्रीम्स कैसे खोलते हैं, उनसे बाइट्स कैसे पढ़ते हैं और वे कर्सर की पोजीशन कैसे मूव करते हैं।
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 मेथड बाइट्स को 512 बाइट्स के चंक्स में आंतरिक @buffer वेरिएबल में जोड़ता है जब तक @buffer वेरिएबल में न्यूलाइन शामिल न हो। जब ऐसा होता है, तो यह बफर को पहले न्यूलाइन द्वारा विभाजित करता है। पहला भाग line है, और दूसरा भाग नया बफर है।
पास किया गया ब्लॉक तब लाइन के साथ कॉल किया जाता है और शेष @buffer को अगले लूप में उपयोग के लिए रखा जाता है।
फाइल के कंटेंट को बफर करके, I/O कॉल्स की संख्या कम हो जाती है जबकि फाइल को तार्किक चंक्स में विभाजित किया जाता है।
फाइल्स स्ट्रीमिंग
सारांश में, फाइल्स स्ट्रीमिंग ऑपरेटिंग सिस्टम के कर्नेल से फाइल खोलने के लिए कहकर काम करती है, फिर उससे बिट दर बिट बाइट्स पढ़ती है। जब रूबी में फाइल को प्रति लाइन पढ़ा जाता है, तो डेटा को फाइल से एक बार में 512 बाइट्स लिया जाता है और उसके बाद "लाइन्स" में विभाजित किया जाता है।
यह रूबी में I/O और फाइल्स स्ट्रीमिंग का हमारा अवलोकन समाप्त करता है। हमें यह जानकर खुशी होगी कि आपने इस लेख के बारे में क्या सोचा, या यदि आपके कोई प्रश्न हैं। हम जांच और व्याख्या करने के लिए विषयों की तलाश में हमेशा रहते हैं, इसलिए यदि रूबी में कुछ जादुई है जिसे आप पढ़ना चाहेंगे, तो @AppSignal पर हमें बताने में संकोच न करें!