1. 程式人生 > >C#中MemoryStream類的介紹

C#中MemoryStream類的介紹

MemoryStream位於System.IO名稱空間,為系統記憶體提供流式的讀寫操作。常作為其他流資料交換時的中間物件操作。

1、MemoryStream類封裝一個位元組陣列,在構造例項時可以使用一個位元組陣列作為引數,但是陣列的長度無法調整。使用預設無引數建構函式建立例項,可以使用Write方法寫入,隨著位元組資料的寫入,陣列的大小自動調整。

2、在對MemoryStream類中資料流進行讀取時,可以使用seek方法定位讀取器的當前的位置,可以通過指定長度的陣列一次性讀取指定長度的資料。ReadByte方法每次讀取一個位元組,並將位元組返回一個整數值。

3、UnicodeEncoding類中定義了Unicode中UTF-16編碼的相關功能。通過其中的方法將字串轉換為位元組,也可以將位元組轉換為字串。 MemoryStream 是一個特例,MemoryStream中沒有任何非託管資源,所以它的Dispose不呼叫也沒關係。託管資源.Net會自動回收

MemoryStream繼承自Stream類。記憶體流的好處是指標可以晃來晃去,也就是支CanSeek,Position,Seek()。任意讀其中一段。

在記憶體流中有必要了解一下SeekOrigin列舉

列舉成員 成員值 描述
Begin 0 指定流的開頭。
Current 1 指定流內的當前位置。
End 2 指定流的結尾。

MemoryStream提供的屬性與方法: 一、屬性

CanRead     已重寫。獲取一個值,該值指示當前流是否支援讀取。 CanSeek     已重寫。獲取一個值,該值指示當前流是否支援查詢。 CanTimeout    獲取一個值,該值確定當前流是否可以超時。(從 Stream 繼承。) CanWrite     已重寫。獲取一個值,該值指示當前流是否支援寫入。 Capacity     獲取或設定分配給該流的位元組數。 這個是分配的位元組數 Length      已重寫。獲取用位元組表示的流長度。這個是真正佔用的位元組數。 Position      已重寫。獲取或設定流中的當前位置。 ReadTimeout   獲取或設定一個值,該值確定流在超時前嘗試讀取多長時間。 (從 Stream 繼承。) WriteTimeout   獲取或設定一個值,該值確定流在超時前嘗試寫入多長時間。 (從 Stream 繼承。)

二、方法

BeginRead     開始非同步讀操作。 (從 Stream 繼承。) BeginWrite    開始非同步寫操作。 (從 Stream 繼承。) Close        關閉當前流並釋放與之關聯的所有資源(如套接字和檔案控制代碼)。 (從 Stream 繼承。) CreateObjRef   建立一個物件,該物件包含生成用於與遠端物件進行通訊的代理所需的全部相關資訊。 (從 MarshalByRefObject 繼承。) Dispose      已過載。 EndRead     等待掛起的非同步讀取完成。 (從 Stream 繼承。) EndWrite      結束非同步寫操作。 (從 Stream 繼承。) Flush        已重寫。 重寫 Stream.Flush 以便不執行任何操作。 GetBuffer     返回從其建立此流的無符號位元組陣列。 是會返回所有分配的位元組,不管用沒用到。 GetLifetimeService      檢索控制此例項的生存期策略的當前生存期服務物件。 (從 MarshalByRefObject 繼承。) InitializeLifetimeService   獲取控制此例項的生存期策略的生存期服務物件。 (從 MarshalByRefObject 繼承。) Read            已重寫。 從當前流中讀取位元組塊並將資料寫入 buffer 中。 搞了好久才弄明白Read()方法的含義,第一個引數,是讀取到的內容要輸出到的位元組陣列,第二個引數是放在第一個引數即要輸出的陣列的位置的偏移量,第三個引數是,要讀取的字元數。 用這個方法你可以任意讀取一段需要的記憶體。注意,Read()方法是從當前流的Position屬性的位置開始讀,這就是為什麼很多人測試的時候,剛剛寫入記憶體的資料,Read()方法無法讀取到內容的原因,因為剛剛寫入記憶體之後,位置恰好是在最後一位了。Read()方法當然讀不到。此方法強大之處在於,你可以從一個記憶體流中讀出你想要的一個片段。 ReadByte          已重寫。 從當前流中讀取一個位元組。 Seek            已重寫。 將當前流中的位置設定為指定值。 SetLength          已重寫。 將當前流的長度設為指定值。 Synchronized        在指定的 Stream 物件周圍建立執行緒安全(同步)包裝。 (從 Stream 繼承。) ToArray          將整個流內容寫入位元組陣列,而與 Position 屬性無關。 Write            已重寫。 使用從緩衝區讀取的資料將位元組塊寫入當前流。 同樣注意下,第二個引數是第一個引數陣列的偏移量就可以了。 WriteByte         已重寫。 將一個位元組寫入當前流中的當前位置。 WriteTo          將此記憶體流的整個內容寫入另一個流中。

以下給出使用示例程式碼:

static void Main(string[] args)
        {
            //屬性測試
            MemoryStream ms = new MemoryStream();
            Console.WriteLine(ms.CanRead);      //True  記憶體流可讀
            Console.WriteLine(ms.CanSeek);      //True  記憶體流支援查詢,指標移來移去的查詢
            Console.WriteLine(ms.CanTimeout);   //False 記憶體流不支援超時
            Console.WriteLine(ms.CanWrite);     //True  記憶體流可寫

            Console.WriteLine(ms.Capacity);     //0     分配給該流的位元組數
            byte[] bytes = Encoding.UTF8.GetBytes("abcdedcba");
            ms.Write(bytes, 0, bytes.Length);   //已將一段文字寫入記憶體
            Console.WriteLine(ms.Capacity);     //256   再次讀取為文字流分配的位元組數已經變成了256,看來記憶體流是根據需要的多少來分配的
            Console.WriteLine(ms.Length);       //9    這個是流長度,通常與英文的字元數一樣,真正佔用的位元組數。

            Console.WriteLine(ms.Position);     //9    流當前的位置,該屬性可讀可設定

            //Console.WriteLine(ms.ReadTimeout);    由於流不支援超時,此屬性如果讀取或者設定的話會報錯
            //Console.WriteLine(ms.WriteTimeout);   由於流不支援超時,此屬性如果讀取或者設定的話會報錯

            //方法測試
            byte[] byte1 = ms.GetBuffer();          //返回無符號位元組陣列 差點被忽悠了,無符號位元組陣列 其實就是byte(0~255),有符號位元組sbyte(-128~127)
            string str1 = Encoding.UTF8.GetString(byte1);
            Console.WriteLine(str1);    //輸出    abcdedcba

            ms.Seek(2, SeekOrigin.Current);    //設定當前流正在讀取的位置 為開始位置即從0開始
            //從記憶體中讀取一個位元組
            int i = ms.ReadByte();
            Console.WriteLine(i);                   //輸出99
            byte[] bytes3 = ms.ToArray();
            foreach (byte b in bytes3)
            {
                Console.Write(b + "-");//用於對比   輸出 97-98-99-100-101-100-99-98-97-   可以看到    0,1,2第二位剛好是99
            }

            MemoryStream ms2 = new MemoryStream();
            byte[] bytes6 = Encoding.UTF8.GetBytes("abcde");
            ms2.Write(bytes6, 0, bytes6.Length);
            Console.WriteLine(ms2.Position);    //輸出5 寫完之後流的位置就到了最後,因此想用read讀取必須加下面這一行程式碼。 

            //ms2.Seek(0, SeekOrigin.Begin);    //想要用Read方法讀取完整的流,必須設定當前位置,Read是從Position的位置開始讀。
            ms2.Position = 0;                   //Read是從當前位置開始讀,這行程式碼和上面一行意義一樣。
            byte[] byteArray = new byte[5] { 110, 110, 110, 110, 110 }; //99是經過YTF8解碼之後是 n
            ms2.Read(byteArray, 2, 1);   //讀取一個位元組,byteArray的第一個元素中,(注意從0開始)
            Console.WriteLine(Encoding.UTF8.GetString(byteArray)); //nnann
            //ms2.Read(byteArray, 2, 2);
            //Console.WriteLine(Encoding.UTF8.GetString(byteArray)); //nnabn    //當超出接收陣列總長度的時候,後面的元素會被移開


            //設定當前流的長度
            Console.WriteLine(ms.Length);   //輸出9   當前流的長度是9
            ms.SetLength(20);
            Console.WriteLine(ms.Length);   //輸出20
            foreach (byte b in ms.ToArray())    //將流的內容也就是記憶體中的內容轉換位元組陣列
            {
                Console.Write(b + "-");     //輸出 97-98-99-100-101-100-99-98-97-0-0-0-0-0-0-0-0-0 由於設定了長度,因此空的自動補0
            }
            Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));   //輸出    abcdedcba   雖然長度變長了,但是沒影響讀取資料

            MemoryStream ms1 = new MemoryStream();
            byte[] bytes4 = ms1.ToArray();
            Console.WriteLine("此記憶體流並沒有寫入資料(Write)" + Encoding.UTF8.GetString(bytes4));//輸出    此記憶體流並沒有寫入資料(Write)  因為記憶體為空


            //下面來一個指定位置的寫入
            MemoryStream ms3 = new MemoryStream();
            byte[] bytesArr = Encoding.ASCII.GetBytes("abcdefg");
            ms3.Write(bytesArr, 0, bytesArr.Length);
            ms3.Position = 2;
            ms3.WriteByte(97);  //97代表的是a   這段程式碼的意思是,將原先第二個的c替換為a
            string str = Encoding.ASCII.GetString(ms3.ToArray());
            Console.WriteLine(str); //輸出 abacdefg
            
            byte[] byteArr1 = Encoding.ASCII.GetBytes("kk");
            ms3.Position = 4;
            ms3.Write(byteArr1, 0, byteArr1.Length);
            Console.WriteLine(Encoding.UTF8.GetString(ms3.ToArray()));  //abadkkg   //從第4位替換掉了兩個位元組為KK

            Console.ReadKey();
        }

接下來實現資料類的轉換:

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

public class DataSwitch
{

    /// <summary>
    /// 資料類物件轉成位元組流
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    ///   //MemoryStream: 建立其支援儲存區為記憶體的流。
        //IFormatter : 提供將序列化物件格式化的功能。
    public static byte[] ObjectToBytes(object obj)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            // //以二進位制格式將物件或整個連線物件圖形序列化和反序列化。
            IFormatter formatter = new BinaryFormatter();
            //把字串以二進位制放進memStream中
            formatter.Serialize(ms, obj);
            //返回從其建立此流的無符號位元組陣列。 是會返回所有分配的位元組,不管用沒用到。
            ////返回無符號位元組陣列 ,無符號位元組陣列 其實就是byte(0~255),有符號位元組sbyte(-128~127)
            return ms.GetBuffer();
        }
    }

    /// <summary>
    /// 位元組流轉成資料類物件
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    public static object BytesToObject(byte[] bytes)
    {
        using (MemoryStream ms = new MemoryStream(bytes))
        { 
            // //以二進位制格式將物件或整個連線物件圖形序列化和反序列化。
            IFormatter formatter = new BinaryFormatter();
            //把字串以二進位制放進memStream中
            return formatter.Deserialize(ms);
        }
    }
}