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.mzph.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 ->…

.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…

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…

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…

saltstack 主题说明

转载于:https://www.cnblogs.com/40kuai/p/9335869.html

基于spring boot 的ssm项目的简单配置

2019独角兽企业重金招聘Python工程师标准>>> 我前面的帖子有介绍spring boot的简单搭建&#xff0c;现在我再讲讲spring boot的简单配置 首先&#xff0c;项目结构 启动类 RestController 注解相当于ResponseBody &#xff0b; Controller合在一起的作用。 Sprin…

nest 架构_如何与其他人分享您的Nest Cam Feed

nest 架构Your Nest Cam can help you keep an eye on your home from anywhere you are, but more eyes you trust to watch your stuff is more comforting. If you want someone else to check in once in a while, you can share your Nest Cam feed with a simple, passwo…

.Net 和Assembly下滑其它回升,TIOBE编程语言2022年12排行榜

楔子TIOBE编程语言排行榜一般反应的是语言的生态&#xff0c;个人比较喜欢这个排行。来看下2022年最后一个月12月&#xff0c;最后一天,TIOBE的排行榜单。榜单分析这里只看下前10名的编程语言&#xff0c;里面非常显眼的是所有的语言都增加了生态环境&#xff0c;包括不被看好的…

Haproxy安装与配置

Haproxy安装与配置 有关高负载均衡的软件&#xff0c;目前使用比较多的是haproxy、nginx和lvs。下面我们就开始学习haprxoy这款软件。 1、Haproxy概念 1.1、haproxy原理 haproxy提供高可用性、负载均衡以及基于TCP(第四层)和HTTP&#xff08;第七层&#xff09;应用的代理&…