内部:Rubyでのファイルの「スラーピング」とストリーミング
今回のRubyMagicでは、Rubyでのストリーミングファイルと、IO
について学習します。 クラスは、ファイルをメモリに完全にロードせずにファイルの読み取りを処理し、読み取りバイトをバッファリングすることによって1行ごとにファイルを読み取る方法を処理します。さっそく飛び込みましょう!
「スラーピング」およびストリーミングファイル
Rubyの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"
内部的には、これはファイルを開き、そのコンテンツを読み取り、ファイルを閉じて、コンテンツを単一の文字列として返します。ファイルのコンテンツを一度に「スラップ」することで、Rubyのガベージコレクタによってクリーンアップされるまでメモリに保持されます。
例として、ファイル内のすべての文字を大文字にして、別のファイルに書き込みたいとします。 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がアプリのプロセスを強制終了する可能性があります。
幸い、RubyではFile.foreach
を使用してファイルを1行ずつ読み取ることができます。 。ファイルの全内容を一度に読み取る代わりに、各行に対して渡されたブロックを実行します。
その結果は列挙可能であるため、各行にブロックを生成するか、ブロックが渡されない場合は列挙子オブジェクトを返します。これにより、すべてのコンテンツを一度にメモリにロードしなくても、大きなファイルを読み取ることができます。
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"
ファイル全体を大文字にするには、入力ファイルから1行ずつ読み取り、大文字にして、出力ファイルに追加します。
irb> File.open("upcased.log", "a") do |output|
irb* File.foreach("production.log") { |line| output.write(line.upcase) }
irb> end
=> nil
では、最初にファイル全体を読み取る必要なしに、ファイルを1行ずつ読み取るとどのように機能するのでしょうか。それを理解するには、ファイルの読み取りの周りのいくつかのレイヤーを剥がす必要があります。 RubyのIO
を詳しく見てみましょう。 クラス。
I/OとRubyのIO
クラス
File.read
であっても およびFile.foreach
存在する場合、File
のドキュメント クラスはそれらをリストしません。実際、File
にはファイルの読み取りまたは書き込みメソッドはありません。 親のIO
から継承されているため、クラスのドキュメント クラス。
I / O
I/Oデバイス は、キーボード、ディスプレイ、ハードドライブなど、コンピューターとの間でデータを転送するデバイスです。 入力/出力を実行します 、または I / O 、データのストリームを読み取るか生成することによって。
ハードドライブからのファイルの読み取りと書き込みは、遭遇する最も一般的なI/Oです。他のタイプのI/Oには、ソケット通信、端末へのログ出力、およびキーボードからの入力が含まれます。
IO
Rubyのクラスは、ファイルの読み取りや書き込みなど、すべての入力と出力を処理します。ファイルの読み取りは他の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ストリームの読み取り
内部的には、RubyのIO
クラスの読み取りおよび書き込み機能は、カーネルシステムコールに関する抽象化に基づいています。オペレーティングシステムのカーネルが、I/Oデバイスからの読み取りとI/Oデバイスへの書き込みを処理します。
ファイルを開く
IO.sysopen
カーネルにファイルへの参照をファイルテーブルに配置するように要求し、プロセスのファイル記述子テーブルにファイル記述子を作成することにより、ファイルを開きます。
ファイル記述子とファイルテーブル
ファイルを開くと、ファイル記述子(I / Oリソースへのアクセスに使用される整数)が返されます。
各プロセスには、ファイル記述子をメモリに保持するための独自のファイル記述子テーブルがあり、各記述子は、システム全体のファイルテーブルのエントリを指します。 。
I /Oリソースからの読み取りまたはI/Oリソースへの書き込みのために、プロセスはシステムコールを介してファイル記述子をカーネルに渡します。プロセスはファイルテーブルにアクセスできないため、カーネルはプロセスに代わってファイルにアクセスします。
ファイルを開くと コンテンツをメモリに保持しますが、ファイル記述子テーブルがいっぱいになる可能性があるため、ファイルを開いた後は常にファイルを閉じることをお勧めします。 File.open
をラップするメソッド File.read
のように これは自動的に行われ、ブロックされたものも同様です。
この例では、IO.sysopen
を呼び出して、さらに一歩進んでいます。 直接メソッド。このメソッドは、ファイル名を渡すことにより、後で開いているファイルを参照するために使用できるファイル記述子を作成します。
irb> IO.sysopen("log/production.log")
=> 9
IO
を作成するには Rubyが読み取りおよび書き込みを行うためのインスタンスとして、ファイル記述子を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バイトを再度読み取ることができます。
ファイルを1行ずつ読み取る
これで、IO
クラスの便利なメソッドはIOストリームを開き、それらからバイトを読み取り、カーソルの位置をどのように移動するかを示します。
IO.foreach
のようなメソッド およびIO#gets
バイト数ごとではなく、行ごとに要求できます。次の改行を見つけてその位置まですべてのバイトを取得するためのパフォーマンスの高い方法はないため、Rubyはファイルのコンテンツの分割を処理する必要があります。
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
を使用してファイルからバイトを取得します バイトが$/
になるまで、一度に1つずつ 、改行を示します。改行が見つかると、バイトの取得を停止し、渡されたブロックをその行で呼び出します。
このソリューションは機能しますが、IO.sysread
を呼び出すため非効率的です ファイル内のすべてのバイトに対して。
ファイルコンテンツのバッファリング
Rubyは、ファイルのコンテンツの内部バッファーを保持することにより、これをどのように行うかについてより賢明です。一度に1バイトずつファイルを読み取る代わりに、一度に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
、2番目の部分は新しいバッファです。
渡されたブロックは、その行と残りの@buffer
で呼び出されます。 次のループで使用するために保持されます。
ファイルのコンテンツをバッファリングすることにより、ファイルを論理チャンクに分割する際のI/O呼び出しの数が削減されます。
ストリーミングファイル
要約すると、ストリーミングファイルは、オペレーティングシステムのカーネルにファイルを開くように要求し、ファイルからバイトを少しずつ読み取ることで機能します。 Rubyで1行ごとにファイルを読み取る場合、データは一度に512バイトずつファイルから取得され、その後「行」に分割されます。
これで、RubyでのI/Oとストリーミングファイルの概要は終わりです。この記事についてのご意見やご不明な点がございましたら、お気軽にお問い合わせください。私たちは常に調査と説明が必要なトピックを探しています。そのため、Rubyに魔法のようなものがあれば、遠慮なく@AppSignalまでお問い合わせください。
-
整理整頓:Mac ファイルの整理と整理
大量のファイルを扱う Mac ユーザーの場合、専用ツールを使用して Mac ファイルを整理および整理 船の形に小さなパイルが散らかるのを防ぎます。ファイルが別々の場所に散らばっていると、干し草の山に針を刺したり、偶発的にデータを失ったりする可能性があります。 ファイルを整理してふるいにかける方法の詳細を知ることで、手動プロセスのストレスをすばやく回避できます。 Finder の組み込みオプションを使用すると、ファイルをきれいに整理できます。このガイドでは、時間と余分な労力を節約し、ストレスを解消するための効率的で実証済みの方法について説明します。 ナイフのようにバターを切り裂く整理ツール
-
写真をプレビューして適切なファイルを削除する方法
重複したイメージは、不要なストレージ スペースを占有することで、世界中のコンピューターに大混乱をもたらしてきました。この問題の解決策は、重複画像検索ソフトウェアを使用して重複画像を削除することです。ただし、どの画像が削除されているのか確信が持てない場合があります。 したがって、写真を削除する前にプレビューすることが重要です。Duplicate Photos Fixer Pro は、それを可能にする完璧なアプリケーションです。重複した画像を削除する前に、Windows 10 の画像プレビューを使用する必要はありませんが、このアプリに組み込まれているプレビュー機能を使用する必要があります。 写真