C# 溫故而知新:Stream篇(五)

MemoryStream

目錄:

1 簡單介紹一下MemoryStream

2 MemoryStream和FileStream的區別

3 通過部分源碼深入了解下MemoryStream

4 分析MemorySteam最常見的OutOfMemory異常

5 MemoryStream 的構造

6 MemoryStream 的屬性

7 MemoryStream 的方法

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 overflowif (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];}elseBuffer.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數組 假設該數組容量是256Mbyte[] 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){//定義一個XMLWriterusing (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.xmlFileStream fs = new FileStream(@"d:\test.xml",FileMode.OpenOrCreate);using (fs){//將內存流注入FileStreamms.WriteTo(fs);if(ms.CanWrite)//釋放緩沖區fs.Flush();}}}}
復制代碼

????? 輸出結果:


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

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

后臺:

復制代碼
  public class ImageHandler : IHttpHandler{#region IHttpHandler Memberspublic 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得到ImageImage 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;//通過內存流讀取到imageBytesms.Read(imageBytes, 0, imageBytes.Length);ms.Close();//返回imageBytesreturn 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,盡請期待!

?

轉載于:https://www.cnblogs.com/Zsundy/p/9325339.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/279839.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/279839.shtml
英文地址,請注明出處:http://en.pswp.cn/news/279839.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

dosbox 自動運行_如何使用DOSBox運行DOS游戲和舊應用

dosbox 自動運行New versions of Windows don’t fully support classic DOS games and other old applications — this is where DOSBox comes in. It provides a full DOS environment that runs ancient DOS apps on modern operating systems. Windows的新版本不完全支持經…

WPF 自定義放大鏡控件

控件名&#xff1a;Magnifier作 者&#xff1a;WPFDevelopersOrg - 驚鏵原文鏈接[1]&#xff1a;https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用.NET40&#xff1b;Visual Studio 2019;實現此功能需要用到 VisualBrush &#xff0c;放大鏡展現使用 Canvas ->…

springboot小筆記

如果默認通過IDEA的springboot 插件布置的 的初始啟動類是這樣的&#xff0c;這種就是一個普通的java類&#xff0c;只能以jar打包 package com.how2java.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.Sprin…

.NET實現之(WebBrowser數據采集—續篇)

我們繼續“.NET實現之(WebBrowser數據采集)“系列篇之最后一篇&#xff0c;這篇本人打算主要講解怎么用WebBrowser控件來實現“虛擬”的交互性程序&#xff1b;比如我們用Winform做為宿主容器&#xff0c;用Asp.net做相關收集程序頁面&#xff0c;我們需要通過客戶端填寫相關數…

ipad和iphone切圖_如何在iPhone,iPad和Mac上使消息靜音

ipad和iphone切圖If you use Messages on your iPhone, iPad, or Mac, then you probably know how quickly you can become overrun with message notifications, especially if you’re part of a group message. Thankfully, there’s an easy way to mute specific message…

Pipy 實現 SOCKS 代理

上篇我們介紹了服務網格 osm-edge 出口網關使用的 HTTP 隧道&#xff0c;其處理方式與另一種代理有點類似&#xff0c;就是今天要介紹的 SOCKS 代理。二者的主要差別簡單來說就是前者使用 HTTP CONNECT 告知代理目的地址&#xff0c;而后者則是通過 SOCKS 協議。值得一提的是&a…

python拓展7(Celery消息隊列配置定時任務)

介紹 celery 定時器是一個調度器&#xff08;scheduler&#xff09;&#xff1b;它會定時地開啟&#xff08;kicks off&#xff09;任務&#xff0c;然后由集群中可用的工人&#xff08;worker&#xff09;來執行。 定時任務記錄&#xff08;entries&#xff09;默認 從 beat_s…

Asia Yokohama Regional Contest 2018 G題 What Goes Up Must Come Down(樹狀數組求逆序對)

https://codeforces.com/gym/102082 題意&#xff1a; 給一個數組大小不超過1e5&#xff0c;每個數的值也是1e5以內&#xff0c;可以交換相鄰兩個數&#xff0c;求保證它呈現一個非遞減再非遞增的趨勢的最小交換次數。 題解&#xff1a;對每個數來說&#xff0c;只有兩種情況&a…

Android系統的開機畫面顯示過程分析(8)

3. 第三個開機畫面的顯示過程第三個開機畫面是由應用程序bootanimation來負責顯示的。應用程序bootanimation在啟動腳本init.rc中被配置成了一個服務&#xff0c;如下所示&#xff1a;service bootanim /system/bin/bootanimation user graphics group graphics disabled o…

chrome連接已重置_如何重置(或調整)Chrome的下載設置

chrome連接已重置By default, Chrome saves all downloaded files to the same location—a dedicated “Downloads” folder. The thing is, this isn’t always practical for all types of download files. The good news is you can easily tweak this setting. 默認情況下…

.Net 7 團隊把國內的龍芯確實當做一等公民和棄用的項目

楔子&#xff1a;國內龍芯據說是用的自己的指令集&#xff0c;在研究ILC的時候&#xff0c;發現了龍芯在微軟那邊確實是一等公民的存在。同X64,ARM,X86一同并列交叉編譯和二進制提取。龍芯官網龍芯平臺.NET&#xff0c;是龍芯公司基于開源社區.NET獨立研發適配的龍芯版本&#…

戴爾押寶iSCSI,由低到高組合成型

戴爾&#xff08;Dell&#xff09;是較早接受SAS技術的主流存儲廠商之一&#xff0c;2006年已推出采用SAS硬盤驅動器的SAS直連存儲&#xff08;DAS&#xff09;系統PowerVault MD3000。一年之后&#xff0c;主機連接改用iSCSI的PowerVault MD3000i問世。2008年1月&#xff0c;E…

仿Gin搭建自己的web框架(七)

本篇介紹HTTP Basic Auth的實現以及Recovery機制。 HTTP Basic Auth Basic Auth是一種開放平臺認證方式&#xff0c;簡單的說就是需要你輸入用戶名和密碼才能繼續訪問。對于Basic Auth的概念不過多的進行介紹&#xff0c;直接進入如何實現的過程。 Basic Auth說白了就是賬號和密…

canvas高斯模糊算法

對于模糊圖片這個效果的實現&#xff0c;其實css3中的filter屬性也能夠實現&#xff0c;但是這個屬性的兼容性不是很好&#xff0c;所以我們通常不用這種方法實現&#xff0c;而使用canvas配合JS實現。 <span style"white-space:pre"> </span>//高斯模糊…

word中插入公式的快捷鍵_如何使用插入鍵在Word中插入復制的內容

word中插入公式的快捷鍵In Word, the “Insert” key on the keyboard can be used to switch between Insert and Overtype modes. However, it can also be used as a shortcut key for inserting copied or cut content at the current cursor position. 在Word中&#xff0…

微軟終于為 Visual Studio 添加了內置的 Markdown 編輯器

微軟終于為 Visual Studio 添加了內置的 Markdown 編輯器。根據官方博客的介紹&#xff0c;由于收到許多用戶的反饋&#xff0c;微軟決定為 Visual Studio 添加 Markdown 編輯器。開發者下載最新的 Visual Studio 17.5 第 2 個預覽版就能夠使用 Markdown 編輯功能&#xff0c;無…

【經驗分享】Hydra(爆破神器)使用方法

這個也是backtrack下面很受歡迎的一個工具 參數詳解&#xff1a;-R 根據上一次進度繼續破解-S 使用SSL協議連接-s 指定端口-l 指定用戶名-L 指定用戶名字典(文件)-p 指定密碼破解-P 指定密碼字典(文件)-e 空密碼探測和指定用戶密碼探測(ns)-C 用戶名可以用:分割(username:passw…

【東軟實訓】SQL多表鏈接

如果一個查詢同時涉及兩個以上的表&#xff0c;則稱之為鏈接查詢&#xff0c;鏈接查詢是關系數據庫中最主要的查詢&#xff0c;主要包括等值鏈接查詢、非等值鏈接查詢、自身鏈接查詢、外鏈接查詢和復合條件鏈接查詢。 這篇博文我們來對多表鏈接進行學習。 Outline 鏈接的基本概…

博鰲“‘AI+時代’來了嗎”分論壇,嘉賓們有何重要觀點?...

雷鋒網(公眾號&#xff1a;雷鋒網)3月27日消息&#xff0c;正在進行中的博鰲亞洲論壇2019年年會&#xff0c;于2019年3月26日至29日在中國海南博鰲舉辦。今年博鰲論壇的主題為“共同命運 共同行動 共同發展”。今天&#xff0c;在主題為《“AI時代”來了嗎&#xff1f;》分論壇…

一款統計摸魚時長的開源項目

對于我們程序員&#xff0c;在工作中一天8小時&#xff0c;不可能完全在寫代碼了&#xff0c;累了刷刷論壇、群里吹吹牛&#xff0c;這都是非常正常的。雖然一天下來&#xff0c;可能我們都可以按時完成工作&#xff0c;但是我們不知道&#xff0c;時間都花在哪里了&#xff0c…