本文將為大家介紹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 。
【編輯推薦】
- 淺析.NET Framework中XML數據處理
- .net framework3.5新特性2:新增匿名類
- 在.NET Framework 3.5中的WCF通信選項