Stream篇(五)

來源:互聯網
上載者:User

簡單介紹一下MemoryStream

MemoryStream是記憶體流,為系統記憶體提供讀寫操作,由於MemoryStream是通過無符號位元組數組組成的,可以說MemoryStream的效能可以

算比較出色,所以它擔當起了一些其他流進行資料交換時的中間工作,同時可降低應用程式中對臨時緩衝區和臨時檔案的需要,其實MemoryStream

的重要性不亞於FileStream,在很多場合我們必須使用它來提高效能

 

MemoryStream和FileStream的區別

前文中也提到了,FileStream主要對檔案的一系列操作,屬於比較高層的操作,但是MemoryStream卻很不一樣,它更趨向於底層記憶體的操作,這樣

能夠達到更快的速度和效能,也是他們的根本區別,很多時候,操作檔案都需要MemoryStream來實際進行讀寫,最後放入到相應的FileStream中,

不僅如此,在諸如XmlWriter的操作中也需要使用到MemoryStream提高讀寫速度

 

通過部分源碼深入瞭解下MemoryStream

 由於篇幅關係,本篇無法詳細說明其源碼,還請大家海涵,這裡我就簡單介紹下Write()方法的源碼

  public override void Write(byte[] buffer, int offset, int count) {            if (!_isOpen) __Error.StreamIsClosed();            if (!_writable) __Error.WriteNotSupported();            if (buffer==null)                throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));            if (offset < 0)                throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));            if (count < 0)                throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));            if (buffer.Length - offset < count)                throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));                int i = _position + count;            // Check for overflow            if (i < 0)                throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));            if (i > _length) {                bool mustZero = _position > _length;                if (i > _capacity) {                    bool allocatedNewArray = EnsureCapacity(i);                    if (allocatedNewArray)                        mustZero = false;                }                if (mustZero)                    Array.Clear(_buffer, _length, i - _length);                _length = i;            }            if (count <= 8)            {                int byteCount = count;                while (--byteCount >= 0)                    _buffer[_position + byteCount] = buffer[offset + byteCount];            }            else                Buffer.InternalBlockCopy(buffer, offset, _buffer, _position, count);            _position = i;            return;        }

關於MemoryStream的源碼大家可以自己學習,這裡主要分析下MemoryStream最關鍵的Write()方法,自上而下,最開始的一系列判斷大家很容易看明白,

以後對有可能發生的異常應該了如指掌了吧,判斷後會取得這段資料的長度 int i=_position+count ,接下來會去判斷該資料的長度是否超過了該流的長度,

如果超過再去檢查是否在流的可支配容量(位元組)之內,(注意下EnsureCapacity方法,該方法會自動擴容stream的容量,但是前提條件是你使用了memoryStream

的第二個建構函式,也就是帶有參數是Capaciy)如果超過了流的可支配容量則將尾巴刪除(將超過部分的資料清除),接下來大家肯定會問,為什麼要判斷count<=8,

其實8這個數字在流中很關鍵,個人認為微軟為了效能需要而這樣寫:當位元組小於8時則一個個讀,當位元組大於八時則用block拷貝的方式,在這個範圍內遞減迴圈

將資料寫入流中的緩衝_buffer中,這個緩衝_buffe是memoryStream的一個私人byte數群組類型,流通過讀取外部byte資料放入內部那個緩衝buffer中,如果流

的長度超過了8,則用Buffer.InternalBloackCopy方法進行數組複製,不同於Array.Copy 前者是採用記憶體位移而非索引位移所以效能上有很大的提升。其實

這個方法的原形是屬於c++中的。

 

分析MemorySteam最常見的OutOfMemory異常

先看下下面一段很簡單的測試代碼

//測試byte數組 假設該數組容量是256M            byte[] testBytes=new byte[256*1024*1024];            MemoryStream ms = new MemoryStream();            using (ms)            {                for (int i = 0; i < 1000; i++)                {                    try                    {                        ms.Write(testBytes, 0, testBytes.Length);                    }                    catch                    {                        Console.WriteLine("該記憶體流已經使用了{0}M容量的記憶體,該記憶體流最大容量為{1}M,溢出時容量為{2}M",                             GC.GetTotalMemory(false) / (1024 * 1024),//MemoryStream已經消耗記憶體量                            ms.Capacity / (1024 * 1024), //MemoryStream最大的可用容量                            ms.Length / (1024 * 1024));//MemoryStream當前流的長度(容量)                        break;                    }                }            }            Console.ReadLine();

由於我們設定了一個256M的byte(有點恐怖),看下溢出時的狀態

從輸出結果看,MemoryStream預設可用最大容量是512M  發生異常時正好是其最大容量,聰明的你肯定會問:如果同時使用2個MemoryStream甚至於多個記憶體

是怎麼分配的?很好,還是用代碼來看下輸出結果,可以明顯看出記憶體平均分給了2個MemoryStream但是最大容量還是512M

但是問題來了,假設我們需要操作比較大的檔案,該怎麼辦呢?其實有2種方法能夠搞定,一種是前文所說的分段處理,我們將byte數組分成等份進行

處理,還有一個方法便是盡量增加MemoryStream的最大可用容量(位元組),我們可以在聲明MemoryStream建構函式時利用它的重載版本:

MemoryStream(int capacity)

到底怎麼使用哪種方法比較好呢?其實筆者認為具體項目具體分析,前者分段處理的確能夠解決大資料量操作的問題,但是犧牲了效能和時間(多線程暫

時不考慮),後者可以得到效能上的優勢但是其允許的最大容量是 int.MAX,所以無法給出一個明確的答案,大家在做項目按照需求自己定製即可,最關鍵

的還是要取到效能和開銷的最佳點位

         還有一種更噁心的溢出方式,往往會讓大家抓狂,就是不定時溢出,就是MemoryStream處理的檔案可能只有40M或更小時也會發生OutOfMemory

的異常,關於這個問題,終於在老外的一篇文章中得到瞭解釋,運氣不錯,陳彥銘大哥在他的部落格中正好翻譯了下,免去我翻譯的工作^^,由於這個牽涉到

windows的記憶體機制,包括
記憶體頁,進程的虛擬位址空間等,比較複雜,所以大家看他的這篇文章前,我先和大家簡單介紹下頁和進程的虛擬位址

記憶體頁:記憶體頁分為:檔案頁和計算頁
記憶體中的檔案頁是檔案快取區,即檔案型的記憶體頁,用於存放檔案資料的記憶體頁(也稱永久頁),作用在於讀寫檔案時可以減少對磁碟的訪問,如果它的大小

設定得太小,會引起系統頻繁地訪問磁碟,增加磁碟I/O;設定太大,會浪費記憶體資源。記憶體中的計算頁也稱為計算型的記憶體頁,主要用於存放程式碼和臨

時使用的資料

進程的虛擬位址:每一個進程被給予它的非常私人的虛擬位址空間。對於32位的進程,地址空間是4G因為一個32位指標能夠有從0x00000000到0xffffffff之

間的任意值。這個範圍允許指標有從4294967296個值的一個,覆蓋了一個進程的4G範圍。對於64位進程,地址空間是16eb因為一個64位指標能夠指向

18,446,744,073,709,551,616個值中的一個,覆蓋一個進程的16eb範圍。這是十分寬廣的範圍。

上述概念都來自windows核心編程 這本書,其實這本書對我們程式員來說很重要,對於記憶體的操作,本人也是小白,看來這本書非買不可了。。。。

 

MemoryStream 的構造

MemoryStream()

MemoryStream 允許不帶參數的構造

 

MemoryStream(byte[] byte)

Byte數組是包含了一定的資料的byte數組,這個構造很重要,初學者或者用的不是很多的程式員會忽略這個構造導致後面讀取或寫入資料時發現memoryStream中

沒有byte資料,會導致很鬱悶的感覺,大家注意下就行,有時也可能無需這樣,因為很多方法傳回值已經是MemoryStream了

 

MemoryStream(int capacity)

這個是重中之重,為什麼這麼說呢?我在本文探討關於OutOfMemory異常中也提到了,如果你想額外提高MemoryStream的輸送量(位元組),也只能靠這個方法提升

一定的輸送量,最多也只能到int.Max,這個方法也是解決OutOfMemory的一個可行方案

 

MemoryStream(byte[] byte,
bool writeable)

Writeable參數定義該流是否可寫

 

MemoryStream(byte[] byte,
int index, int count)

Index 參數定義從byte數組中的索引index,

Count  參數是擷取的資料量的個數

 

MemoryStream(byte[] byte,int index,int count,
bool writeable,bool publiclyVisible)

publiclyVisible 參數表示true 可以啟用 GetBuffer方法,它返回無符號位元組數組,流從該數組建立;否則為 false,(大家一定覺得這很難理解,別急下面的方法中

我會詳細講下這個東東)

 

 MemoryStream 的屬性

Memory 的屬性大致都是和其父類很相似,這些功能在我的這篇中已經詳細討論過,所以我簡單列舉一下其屬性:  

其專屬的屬性:

Capacity:這個前文其實已經提及,它表示該流的可支配容量(位元組),非常重要的一個屬性

 

MemoryStream 的方法

對於重寫的方法這裡不再重複說明,大家可以參考我寫的第一篇

以下是memoryStream專屬的方法

virtual byte[] GetBuffer()

這個方法使用時需要小心,因為這個方法返回無符號位元組數組,也就是說,即使我只輸入幾個字元例如”HellowWorld”我們只希望返回11個資料就行,

可是這個方法會把整個緩衝區的資料,包括那些已經分配但是實際上沒有用到的位元組資料都返回出來,如果想啟用這個方法那必須使用上面最後一個構

造函數,將publiclyVisible屬性設定成true就行,這也是上面那個建構函式的作用所在

 

virtual void WriteTo(Streamstream)

這個方法的目的其實在本文開始時討論效能問題時已經指出,memoryStream常用起中間流的作用,

所以讀寫在處理完後將記憶體流寫入其他流中

 

 簡單樣本 XmlWriter中使用MemoryStream

/// <summary>        /// 示範在xmlWriter中使用MemoryStream        /// </summary>        public static void UseMemoryStreamInXMLWriter()        {            MemoryStream ms = new MemoryStream();            using (ms)            {                //定義一個XMLWriter                using (XmlWriter writer = XmlWriter.Create(ms))                {                    //寫入xml頭                    writer.WriteStartDocument(true);                    //寫入一個元素                    writer.WriteStartElement("Content");                    //為這個元素新增一個test屬性                    writer.WriteStartAttribute("test");                    //設定test屬性的值                    writer.WriteValue("逆時針的風");                    //釋放緩衝,這裡可以不用釋放,但是在實際項目中可能要考慮部分釋放對效能帶來的提升                    writer.Flush();                    Console.WriteLine("此時記憶體使用量量為:{2}KB,該MemoryStream的已經使用的容量為{0}byte,預設容量為{1}byte",                        Math.Round((double)ms.Length, 4), ms.Capacity,GC.GetTotalMemory(false)/1024);                    Console.WriteLine("重新置放前MemoryStream所在的位置是{0}",ms.Position);                    //將流中所在的當前位置往後移動7位,相當於空格                    ms.Seek(7, SeekOrigin.Current);                    Console.WriteLine("重新置放後MemoryStream所在的位置是{0}", ms.Position);                    //如果將流所在的位置設定為如下所示的位置則xml檔案會被打亂                    //ms.Position = 0;                    writer.WriteStartElement("Content2");                    writer.WriteStartAttribute("testInner");                    writer.WriteValue("逆時針的風Inner");                    writer.WriteEndElement();                    writer.WriteEndElement();                    //再次釋放                    writer.Flush();                    Console.WriteLine("此時記憶體使用量量為:{2}KB,該MemoryStream的已經使用的容量為{0}byte,預設容量為{1}byte",                        Math.Round((double)ms.Length, 4), ms.Capacity, GC.GetTotalMemory(false)/1024);                    //建立一個FileStream  檔案建立目的地是d:\test.xml                    FileStream fs = new FileStream(@"d:\test.xml",FileMode.OpenOrCreate);                    using (fs)                    {                        //將記憶體流注入FileStream                        ms.WriteTo(fs);                        if(ms.CanWrite)                          //釋放緩衝區                        fs.Flush();                    }                }            }        }

  輸出結果:

簡單樣本:自訂一個處理圖片的HttpHandler

 有時項目裡我們必須將圖片進行一定的操作,例如浮水印,下載等,為了方便和管理我們可以自訂一個HttpHander 來負責這些工作

後台:

  public class ImageHandler : IHttpHandler    {        #region IHttpHandler Members        public bool IsReusable        {            get { return true; }        }        /// <summary>        /// 實現IHTTPHandler後必須實現的方法        /// </summary>        /// <param name="context">HttpContext上下文</param>        public void ProcessRequest(HttpContext context)        {            context.Response.Clear();            //得到圖片名            var imageName = context.Request["ImageName"] == null ? "逆時針的風"                : context.Request["ImageName"].ToString();            //得到圖片ID,這裡只是示範,實際項目中不是這麼做的            var id = context.Request["Id"] == null ? "01"                : context.Request["Id"].ToString();            //得到圖片地址            var stringFilePath = context.Server.MapPath(string.Format("~/Image/{0}{1}.jpg", imageName, id));            //聲明一個FileStream用來將圖片暫時放入流中            FileStream stream = new FileStream(stringFilePath, FileMode.Open);            using (stream)            {                //透過GetImageFromStream方法將圖片放入byte數組中                byte[] imageBytes = this.GetImageFromStream(stream,context);                //上下文確定寫到客戶短時的檔案類型                context.Response.ContentType = "image/jpeg";                //上下文將imageBytes中的資料寫到前段                context.Response.BinaryWrite(imageBytes);                stream.Close();            }        }        /// <summary>        /// 將流中的圖片資訊放入byte數組後返回該數組        /// </summary>        /// <param name="stream">檔案流</param>        /// <param name="context">上下文</param>        /// <returns></returns>        private byte[] GetImageFromStream(FileStream stream, HttpContext context)        {            //通過stream得到Image            Image image = Image.FromStream(stream);            //加上浮水印            image = SetWaterImage(image, context);            //得到一個ms對象            MemoryStream ms = new MemoryStream();            using (ms)            {               //將圖片儲存至記憶體流                image.Save(ms, ImageFormat.Jpeg);                byte[] imageBytes = new byte[ms.Length];                ms.Position = 0;                //通過記憶體流讀取到imageBytes                ms.Read(imageBytes, 0, imageBytes.Length);                ms.Close();                //返回imageBytes                return imageBytes;            }        }        /// <summary>        /// 為圖片加上浮水印,這個方法不用在意,只是示範,所以沒加透明度        /// 下次再加上吧        /// </summary>        /// <param name="image">需要加浮水印的圖片</param>        /// <param name="context">上下文</param>        /// <returns></returns>        private Image SetWaterImage(Image image,HttpContext context)         {            Graphics graphics = Graphics.FromImage(image);            Image waterImage = Image.FromFile(context.Server.MapPath("~/Image/逆時針的風01.jpg"));            //在大圖右下角畫上浮水印圖就行            graphics.DrawImage(waterImage,                new Point {                     X = image.Size.Width - waterImage.Size.Width,                    Y = image.Size.Height - waterImage.Size.Height                 });            return image;        }        #endregion    }

別忘了還要在Web.Config中進行配置,別忘記verb和path屬性,否則會報錯

  <httpHandlers>      <add type="ImageHandler.ImageHandler,ImageHandler"  verb="*" path="ImageHandler.apsx"/>    </httpHandlers>

這樣前台便能使用了

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">    <h2>        About    </h2>    <p>        Put content here.        <asp:Image runat="server" ImageUrl="ImageHandler.apsx?ImageName=逆時針的風&Id=02" />    </p></asp:Content>

輸出結果

轉自部落格員:逆時針の風

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.