• 艾伟:浅谈 Stream.Read 方法 狼人:


    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 中。流中的当前位置提升已读取的字节数;但是,如果出现异常,流中的当前位置保持不变。实现返回已读取的字节数。仅当位置当前位于流的末尾时,返回值才为零。如果没有任何可用的数据,该实现将一直阻塞到至少有一个字节的数据可读为止。仅当流中不再有其他的数据,而且也不再需要更多的数据(如已关闭的套接字或文件尾)时,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", "ben@skyiv.com").
            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);
        }
      }
    }
    

    将这个程序运行三次的结果如下:

                    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
    

    可见,Stream.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("ArgumentOutOfRan
    ge_NeedNonNegNum"
    )); } if (count < 0) { throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRa
    nge_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("ArgumentOutOfRan
    ge_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 文档中对这两个方法的描述:

    • BinaryReader.Read 方法:将 index 作为字节数组中的起始点,从流中读取 count 个字节。
    • BinaryReader.ReadBytes 方法:从当前流中将 count 个字节读入字节数组,并使当前位置提升 count 个字节。
    • 上述两个方法的备注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();
        }
      }
    }
    

    我在上一篇随笔“【算法】利用有限自动机进行字符串匹配”中给出了一道思考题如下:

    上面的第二个 C# 程序中有一个 bug,但是这个 bug 在绝大多数情况下都不会表现出来。所以这个程序能够 Accepted。

    亲爱的读者,你能够找出这个 bug 吗?

    提示,这个 bug 和字符串匹配算法无关,并且第一个 C# 程序中不存在这个 bug 。

    上述思考题中的第二个 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++] != '\n') ;
      for (int c, q = 0; i < n; q = 0)
      {
        while ((c = s[i++]) != '\n') if (q < 99 && c != '\r') q = delta[q, Array.IndexOf(a, c) + 1];
        Console.WriteLine((q < 4) ? "YES" : "NO");
      }
    }

    这个 bug 至今还没有人找到。实际上,该方法的头两个语句应改为:

    var s = new BinaryReader(Console.OpenStandardInput()).ReadBytes(10000000 + 2 * 1000 + 100);
    int i = 0, n = s.Length;  

    这是因为 Steam.Read 方法在尚未到达流的末尾情况下可以返回少于所请求的字节,这有可能导致只读取了部分输入而产生 bug 。

  • 相关阅读:
    这次安装不太一样
    解惑C#不用释放内存(4)C#为何不用释放内存
    解惑C#不用释放内存(3)C++如何分配内存
    解惑C#不用释放内存(2)分配内存
    解惑C#不用释放内存(1)章节重点
    学习Java异常理解运行期异常
    学习Java声明异常throws
    学习java异常理解编译期异常
    学习java异常-前不久出现的问题
    学习java异常
  • 原文地址:https://www.cnblogs.com/waw/p/2156790.html
Copyright © 2020-2023  润新知