艾伟:浅谈 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("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 文档中对这两个方法的描述:

  • 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 。

时间: 2024-09-29 19:01:37

艾伟:浅谈 Stream.Read 方法的相关文章

浅谈 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 + coun

浅谈python字符串方法的简单使用_python

学习python字符串方法的使用,对书中列举的每种方法都做一个试用,将结果记录,方便以后查询. (1) s.capitalize() ;功能:返回字符串的的副本,并将首字母大写.使用如下: >>> s = 'wwwwww' >>> scap = s.capitalize() >>> scap 'Wwwwww' (2)s.center(width,char); 功能:返回将s字符串放在中间的一个长度为width的字符串,默认其他部分用空格填充,否则使用c

浅谈String.valueOf()方法的使用_基础知识

前面的话 关于类型转换,对象常见的两个方法是toString()和valueOf().实际上,这两个方法也可以应用在包装类型上.前面已经介绍过toString()方法,本文将介绍valueOf()方法,该方法返回原值 [1]undefined和null没有valueOf()方法 undefined.valueOf();//错误 null.valueOf();//错误 [2]布尔型数据true和false返回原值 true.valueOf();//true typeof true.valueOf(

浅谈jquery高级方法描述与应用_jquery

1.addBack() a. third-item的 li 下几个相邻节点(包括third-item) $( "li.third-item" ).nextAll().addBack(). .css( "background-color", "red" ); b. 和end()方法类似,选中的是div.after-addback和p元素,end选中的是div.after-addback元素 $( "div.after-addback&qu

浅谈java的方法覆盖与变量覆盖

首先,我们看看关于重载,和覆盖(重写)的简明定义: 方法重载:如果有两个方法的方法名相同,但参数不一致,哪么可以说一个方法是另一个方法的重载. 方法覆盖:如果在子类中定义一个方法,其名称.返回类型及参数签名正好与父类中某个方法的名称.返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法 我们重点说说覆盖问题,以如下代码为例: [java] view plain copy  print? public class People {       public String getName

浅谈javascript 归并方法_基础知识

ECMAScript5 还新增了2个归并数组的方法:reduce()和reduceRight().  这两个都会迭代数组的所有项         reduce():从第一项开始逐个遍历到最后.         reduceRight():从数组的最后一项开始,遍历到数组的第一项. 这两个方法都接受两个参数:在每一项上调用的函数(参数为:前一个值,当前值,项的索引,数组对象)  这个函数返回的任何值斗殴会作为第一个参数自动传给下一项.第一次迭代发生在数组的第二项上,          因此第一个参

浅谈网站推广方法

在开始写文章的时候,我就给大家声明,我不是什么喜欢奉献的人,我写这篇文章的目的终究还是推广我的网站-经典电影,我只是把这么长时间以来我推广网站的心得给大家说说,如果是高手的话,麻烦你绕到而行. 前几天的时候,我卖了自己的网站,做了差不多半年的时间,心里很舍不得,但是由于种种原因,我还是把这个站卖了.www.dmt06.cn 我不是什么高手,但是,我还是比较喜欢总结的,我把自己这半年以来推广网站的一些方法写出来,是希望更多的新手朋友不要走弯路,不要走偏路了. 1.有道是最笨的方法也是最好的方法.

浅谈网站推广方法之问答平台的推广技巧

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 问答平台顾名思义就是提问,回答的一个互动的交流平台.问答平台的推广一直是网站推广方法中的重要一个方法,它不仅可以给网站带来高质量的外部链接,还有 品牌知名度的提高,最重要的是能够给网站很直接的带来不少的流量.当然,这一切的效果,都是在我们必须利用好,做好这个平台的推广的前提下而得到的.这就 是靠我们怎么去整合网络的资源,来为自己制造出更多的利

浅谈javascript 迭代方法_基础知识

        五个迭代方法 都接受两个参数:要在每一项上运行的函数 和 运行该函数的作用域(可选)         every():对数组中的每一项运行给定函数.如果函数对每一项都返回true,则返回true.         filter():对数组中的每一项运行给定函数.返回该函数会返回true的项组成的数组.         forEach():对数组中每一项运行给定函数.该函数没有返回值.         map():对数组中每一项运行给定函数.返回每次函数调用的结果组成的函数.