.net Stream篇(五)

MemoryStreamhtml

目錄:c++

1 簡單介紹一下MemoryStream程序員

2 MemoryStream和FileStream的區別編程

3 經過部分源碼深刻了解下MemoryStreamwindows

4 分析MemorySteam最多見的OutOfMemory異常數組

5 MemoryStream 的構造緩存

6 MemoryStream 的屬性多線程

7 MemoryStream 的方法ide

8 MemoryStream 簡單示例 :  XmlWriter中使用MemoryStream函數

9 MemoryStream 簡單示例 :自定義一個處理圖片的HttpHandler

10 本章總結

 

 

 

簡單介紹一下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(Stream stream)

這個方法的目的其實在本文開始時討論性能問題時已經指出,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>
複製代碼

輸出結果

 

 本章總結

  本章主要介紹了MemoryStream 的一些概念,異常,結構,包括如何使用,如何解決一些異常等,感謝你們一直支持和鼓勵,文中如出現錯誤還請你們海涵,深夜寫文不容易,

  還請你們多多關注,下篇會介紹BufferedStream,盡請期待!

相關文章
相關標籤/搜索