রুবি ম্যাজিকের এই সংস্করণে, আমরা রুবিতে ফাইল স্ট্রিমিং সম্পর্কে শিখব, কিভাবে 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 এবং রুবির 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#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 স্ট্রীমগুলিকে খোলে, তাদের থেকে বাইটগুলি পড়ে এবং কীভাবে তারা কার্সারের অবস্থানকে সরিয়ে দেয়৷
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-এ আমাদের জানাতে দ্বিধা করবেন না!