Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

内部: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までお問い合わせください。


  1. 整理整頓:Mac ファイルの整理と整理

    大量のファイルを扱う Mac ユーザーの場合、専用ツールを使用して Mac ファイルを整理および整理 船の形に小さなパイルが散らかるのを防ぎます。ファイルが別々の場所に散らばっていると、干し草の山に針を刺したり、偶発的にデータを失ったりする可能性があります。 ファイルを整理してふるいにかける方法の詳細を知ることで、手動プロセスのストレスをすばやく回避できます。 Finder の組み込みオプションを使用すると、ファイルをきれいに整理できます。このガイドでは、時間と余分な労力を節約し、ストレスを解消するための効率的で実証済みの方法について説明します。 ナイフのようにバターを切り裂く整理ツール

  2. 写真をプレビューして適切なファイルを削除する方法

    重複したイメージは、不要なストレージ スペースを占有することで、世界中のコンピューターに大混乱をもたらしてきました。この問題の解決策は、重複画像検索ソフトウェアを使用して重複画像を削除することです。ただし、どの画像が削除されているのか確信が持てない場合があります。 したがって、写真を削除する前にプレビューすることが重要です。Duplicate Photos Fixer Pro は、それを可能にする完璧なアプリケーションです。重複した画像を削除する前に、Windows 10 の画像プレビューを使用する必要はありませんが、このアプリに組み込まれているプレビュー機能を使用する必要があります。 写真