簡単な話だReadメソッド

13673 ワード

Microsoft .NET Framework Base Class LibraryのStream.Readメソッド:
Stream.Readメソッド
派生クラスで書き換えると、現在のストリームからバイトシーケンスが読み込まれ、そのストリームの位置が読み込まれたバイト数だけ上昇します.
構文:
public abstract int Read(byte[] buffer, int offset, int count)
パラメータ:
  • buffer:バイト配列.このメソッドが返されると、バッファには指定された文字配列が含まれ、その配列のoffsetと(offset+count-1)の間の値は現在のソースから読み込まれたバイトに置き換えられます.
  • offset:bufferのゼロから始まるバイトオフセット量で、ここから現在のストリームから読み込まれたデータの格納が開始されます.
  • count:現在のストリームから最も多く読み出すバイト数.

  • 戻り値:
    バッファ内の合計バイト数を読み込みます.現在使用可能なバイト数がリクエストされたバイト数ほど多くない場合、合計バイト数はリクエストされたバイト数よりも小さいか、ストリームの最後に到達した場合はゼロ(0)になります.
    コメント:
    このメソッドのインプリメンテーションは、現在のストリームから最も多くのcountバイトを読み出し、offsetから始まるbufferに格納します.ストリーム内の現在の位置は、読み込まれたバイト数を上げる.ただし、例外が発生した場合、ストリーム内の現在の位置は変更されません.読み込まれたバイト数を返すことを実装します.戻り値は、現在位置がストリームの最後にある場合にのみゼロになります.利用可能なデータがない場合、インプリメンテーションは、少なくとも1バイトのデータが読めるまでブロックされる.Readは、ストリームに他のデータがなくなり、閉じたソケットやファイルの末尾などのより多くのデータが必要なくなった場合にのみ0を返します.ストリームの最後に到達していない場合でも、インプリメンテーションは、要求されたバイトよりも少ないバイトを任意に返すことができる.
    上記のMSDNの最後の一言に注意してください.この点を検証するためにプログラムを書きます.
    using System;
    using System.IO;
    using Skyiv.Util;
    
    namespace Skyiv.Ben.StreamTest
    {
      sealed class Program
      {
        static void Main()
        {
          var bs = new byte[128 * 1024];
          var stream = new FtpClient("ftp://ftp.hp.com", "anonymous", "[email protected]").
            GetDownloadStream("pub/softpaq/allfiles.txt"); // 568,320 bytes
          var br = new BinaryReader(stream);
          Display("Expect", bs.Length);
          Display("Stream.Read", stream.Read(bs, 0, bs.Length));
          Display("BinaryReader.Read", br.Read(bs, 0, bs.Length));
          Display("BinaryReader.ReadBytes", br.ReadBytes(bs.Length).Length);
          Display("Stream.ReadBytes", stream.ReadBytes(bs.Length).Length);
        }
    
        static void Display(string msg, int n)
        {
          Console.WriteLine("{0,22}: {1,7:N0}", msg, n);
        }
      }
    }
    

    このプログラムを3回実行した結果、次のようになります.
                    Expect: 131,072
               Stream.Read:  50,604
         BinaryReader.Read:  11,616
    BinaryReader.ReadBytes: 131,072
          Stream.ReadBytes: 131,072
    
                    Expect: 131,072
               Stream.Read:   1,452
         BinaryReader.Read:   2,904
    BinaryReader.ReadBytes: 131,072
          Stream.ReadBytes: 131,072
    
                    Expect: 131,072
               Stream.Read:   4,356
         BinaryReader.Read: 131,072
    BinaryReader.ReadBytes: 131,072
          Stream.ReadBytes: 131,072
    

    見えますReadメソッドとBinaryReader.Readメソッドは、ストリームの最後に到達していない場合に、要求されたバイトよりも少ないバイトを返すことができる.
    Reflectorを使用してBinaryReaderを表示します.Readメソッドのソース・プログラム・コードは、次のとおりです.
    public virtual int Read(byte[] buffer, int index, int count)
    {
      if (buffer == null)
      {
        throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
      }
      if (index < 0)
      {
        throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
      }
      if (count < 0)
      {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
      }
      if ((buffer.Length - index) < count)
      {
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
      }
      if (this.m_stream == null)
      {
        __Error.FileNotOpen();
      }
      return this.m_stream.Read(buffer, index, count);
    }
    

    上記コードの最後の行のm_streamのタイプはStreamであり,BinaryReaderクラスのベースストリームである.BinaryReader.Readメソッドは必要なチェックをした後、簡単にStreamを呼び出す.Readメソッド.
    そしてBinaryReader.ReadBytesメソッドのソース・プログラム・コードは次のとおりです.
    public virtual byte[] ReadBytes(int count)
    {
      if (count < 0)
      {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
      }
      if (this.m_stream == null)
      {
        __Error.FileNotOpen();
      }
      byte[] buffer = new byte[count];
      int offset = 0;
      do
      {
        int num2 = this.m_stream.Read(buffer, offset, count);
        if (num2 == 0)
        {
          break;
        }
        offset += num2;
        count -= num2;
      }
      while (count > 0);
      if (offset != buffer.Length)
      {
        byte[] dst = new byte[offset];
        Buffer.InternalBlockCopy(buffer, 0, dst, 0, offset);
        buffer = dst;
      }
      return buffer;
    }
    

    上記のコードから分かるように、BinaryReader.ReadBytesメソッドはStreamを循環的に呼び出す.Readメソッドは、ストリームの最後に達するまで、またはcountバイトが読み込まれています.すなわち、このメソッドは、ストリームの最後に到達しなければ、要求されたバイトを必ず返す.
    MSDNドキュメントでは、この2つの方法について説明します.
  • BinaryReader.Readメソッド:indexをバイト配列の開始点としてストリームからcountバイトを読み出します.
  • BinaryReader.ReadBytesメソッド:現在のストリームからcountバイトをバイト配列に読み込み、現在の位置をcountバイトアップします.
  • 上記の2つの方法の注釈:BinaryReaderは、読み取りに失敗した後、ファイルの場所を復元しません.

  • つまり、BinaryReader.ReadメソッドとStream.Readメソッドは、要求されたバイトよりも少ないバイトをストリームの末尾に到達していない場合にも返すことができますが、MSDNドキュメントではこれを指摘していません.プログラムを書くときは、このトラップに落ちないように注意してください.
     
    上記のテストプログラムにはStreamが用いる.ReadBytesメソッドは、拡張メソッドです.ソース・プログラム・コードは次のとおりです.
    using System;
    using System.IO;
    
    namespace Skyiv.Util
    {
      static class ExtensionMethods
      {
        public static byte[] ReadBytes(this Stream stream, int count)
        {
          if (count < 0) throw new ArgumentOutOfRangeException("count", " ");
          var bs = new byte[count];
          var offset = 0;
          for (int n = -1; n != 0 && count > 0; count -= n, offset += n) n = stream.Read(bs, offset, count);
          if (offset != bs.Length) Array.Resize(ref bs, offset);
          return bs;
        }
      }
    }
    

    上記のテストプログラムにはFtpClientクラスも使用されており、私のもう一つのエッセイ「FTPサーバ上の圧縮ファイルを直接処理する方法」を参照してください.そのソースプログラムコードは以下の通りです.
    using System;
    using System.IO;
    using System.Net;
    
    namespace Skyiv.Util
    {
      sealed class FtpClient
      {
        Uri uri;
        string userName;
        string password;
    
        public FtpClient(string uri, string userName, string password)
        {
          this.uri = new Uri(uri);
          this.userName = userName;
          this.password = password;
        }
    
        public Stream GetDownloadStream(string sourceFile)
        {
          Uri downloadUri = new Uri(uri, sourceFile);
          if (downloadUri.Scheme != Uri.UriSchemeFtp) throw new ArgumentException("URI is not an FTP site");
          FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(downloadUri);
          ftpRequest.Credentials = new NetworkCredential(userName, password);
          ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;
          return ((FtpWebResponse)ftpRequest.GetResponse()).GetResponseStream();
        }
      }
    }
    

     
    前回のエッセイ「【アルゴリズム】有限自動機による文字列マッチング」で、次のような考えを示しました.
    上の2番目のC#プログラムにはバグがありますが、このバグはほとんど表現されません.だからこのプログラムはAcceptedできます.
    親愛なる読者、あなたはこのバグを見つけることができますか?
    ヒント:このバグは文字列マッチングアルゴリズムとは無関係であり、最初のC#プログラムにはこのバグは存在しません.
    上記の思考問題の2番目のC#プログラムのMainメソッドは以下の通りです.
    static void Main()
    {
      var s = new byte[10000000 + 2 * 1000 + 100];
      int i = 0, n = Console.OpenStandardInput().Read(s, 0, s.Length);
      while (s[i++] != '
    '
    ) ; for (int c, q = 0; i < n; q = 0) { while ((c = s[i++]) != '
    '
    ) if (q < 99 && c != '\r') q = delta[q, Array.IndexOf(a, c) + 1]; Console.WriteLine((q < 4) ? "YES" : "NO"); } }

    このバグはまだ誰も見つからない.実際には、このメソッドの最初の2つの文を次のように変更します.
    var s = new BinaryReader(Console.OpenStandardInput()).ReadBytes(10000000 + 2 * 1000 + 100);
    int i = 0, n = s.Length;  

    それはStreamのせいだReadメソッドは、ストリームの最後に到達していない場合に、要求されたバイトよりも少ないバイトを返すことができ、これにより、入力の一部のみが読み込まれてバグが発生する可能性がある.