MemoryStream相关知识分享

一、简单介绍一下MemoryStream

MemoryStream是内存流,为系统内存提供读写操作,由于MemoryStream是通过无符号字节数组组成的,可以说MemoryStream的性能可以算比较出色,所以它担当起了一些其他流进行数据交互安时的中间工作,同时可降低应用程序中对临时缓冲区和临时文件的需求,其实MemoryStream的重要性不亚于FileStream,在很多场合,我们必须使用它来提高性能

二、MemoryStream和FileStream的区别

前文中也提到了,FileStream主要对文件的一系列操作,属于比较高层的操作,但是MemoryStream却很不一样,他更趋向于底层内存的操作,这样能够达到更快速度和性能,也是他们的根本区别,很多时候,操作文件都需要MemoryStream来实际进行读写,最后放入相应的FileStream中,不仅如此,在诸如XmlWriter的操作中也需要使用MemoryStream提高读写速度

三、分析MemoryStream最常见的OutOfMemory异常

先看一下下面一段简单的代码

 1             //测试byte数组 假设该数组容量是256M
 2             var testBytes = new byte[256 * 1024 * 1024];
 3             var ms = new MemoryStream();
 4             using (ms)
 5             {
 6                 for (int i = 0; i < 1000; i++)
 7                 {
 8                     try
 9                     {
10                         ms.Write(testBytes, 0, testBytes.Length);
11                     }
12                     catch
13                     {
14                         Console.WriteLine("该内存流已经使用了{0}M容量的内存,该内存流最大容量为{1}M,溢出时容量为{2}M",
15                             GC.GetTotalMemory(false) / (1024 * 1024),//MemoryStream已经消耗内存量
16                             ms.Capacity / (1024 * 1024), //MemoryStream最大的可用容量
17                             ms.Length / (1024 * 1024));//MemoryStream当前流的长度(容量)
18                         break;
19                     }
20                 }
21             }
22             Console.ReadLine();

输出结果:

从输出结果来看,MemoryStream默认可用最大容量是1024M,发生异常时正好是其最大容量。

问题来了,假设我们需要操作比较大的文件,该怎么办呢?其实有2种方法可以搞定,一种是分段处理,我们将Byte数组分成等份进行处理,还有一种方式便是增加MomoryStream的最大可用容量(字节),我们可以在声明MomoryStream的构造函数时利用它的重载版本:MemoryStream(int capacity)

到底使用哪种方法比较好呢?其实笔者认为具体项目具体分析,前者分段处理的确能够解决大数据量操作的问题,但是牺牲了性能和时间(多线程暂时不考虑),后者可以得到性能上的优势,但是其允许最大容量是 int.Max,所以无法给出一个明确的答案,大家在做项目时,按照需求自己定制即可,最关键的还是要取到性能和开销的最佳点位,还有一种更恶心的溢出方式,往往会让大家抓狂,就是不定时溢出,就是MemoryStream处理的文件可能只有40M或更小时,也会发生OutOfMemory的异常,关于这个问题,终于在老外的一篇文章中得到了解释,运气还不错,可以看看这篇博文:一种MemoryStream的替代方案,由于涉及到windows的内存机制,包括内存也,进程的虚拟地址空间等,比较复杂,所以大家看他的文章前,我先和大家简单的介绍一下页和进程的虚拟地址:

内存页:内存页分为:文件页和计算页

内存中的文件页是文件缓存区,即文件型的内存页,用于存放文件数据的内存页(也称永久页),作用在于读写文件时可以减少对磁盘的访问,如果它的大小设置的太小,会引起系统频繁的访问磁盘,增加磁盘I/O,设置太大,会浪费内存资源。

内存中的计算页也称为计算型的内存页,主要用于存放程序代码和临时使用的数据。

进程的虚拟地址:每一个进程被给予它的非常自由的虚拟地址空间。对于32位的进程,地址空间是4G,因为一个32位指针能够从0x00000000到0xffffffff之间的任意值,这个范围允许指针从4294967296个值得一个,覆盖了一个进程得4G范围,对于64位进程,地址空间是16eb因为一个64位指针能够指向18,446,744,073,709,551,616个值中的一个,覆盖一个进程的16eb范围,这是十分宽广的范围,上述概念都在自windows核心编程这本书,其实这本书对于我们程序员来说很重要,对于内存的操作,本人也是小白。

四、MemoryStream的构造函数

1、MemoryStream()

MemoryStream允许不带参数的构造

2、MemoryStream(byte[] byte)

Byte数组是包含了一定数据的byte数组,这个构造很重要,初学者或者用的不是很多的程序员会忽略这个构造函数导致后面读取或写入数据时发现MemoryStream中没有byte数据,会导致很郁闷的感觉,大家注意一下就行,有时候也可能无需这样,因为很多方法返回值已经是MemoryStream了。

3、MemoryStream(int capacity)

这个是重中之重,为什么这么说呢?我在本文探讨关于OutMemory异常中也提到了,如果你想额外提高MemoryStream的吞吐量,也只能靠这个方法提升一定的吞吐量,最多也只能到int.Max,这个方法也是解决OutOfMemory的一个可行方案。

4、MemoryStream(byte[] byte,bool writeable)

writeable参数定义该流是否可写

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

index:参数定义从byte数组中的索引index

count:参数是获取的数据量的个数

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

publiclyVisible:参数表示true可以启用GetBuffer方法,它返回无符号字节数组,流从该数组创建,否则为false。(大家一定觉得这个很难理解,别急,下面的方法中我会详细的讲一下这个东西)

五、MemoryStream的属性

Memory的属性大致都是和其父类很相似,这些功能在我的这篇文章中已经详细讨论过,所以我简单列举以下其属性:

其独有的属性:

Capacity:这个前文其实已经提及,它表示该流的可支配容量(字节),非常重要的一个属性。

六、MemoryStream的方法

对于重写的方法,这里不再重复说明,大家可以去关于Stream的知识分享看一下

以下是MemoryStream独有的方法

1、virtual byte[] GetBuffer()

这个方法使用时需要小心,因为这个方法返回无符号字节数组,也就是说,即使我只输入几个字符例如“HellowWorld”我们只希望返回11个数据就行,可是这个方法会把整个缓冲区的数据,包括那些已经分配但是实际上没有用到的字符数据都返回回来了,如果想启用这个方法那必须使用上面最后一个构造函数,将publiclyVisible属性设置成true就行,这也是上面那个构造函数的错用所在。

2、virtual void WriteTo(Stream stream)

这个方法的目的其实在本文开始的时候讨论性能问题时已经指出,MemoryStream常用起中间流的作用,所以读写在处理完后将内存吸入其他流中。

七、示例:

1、XmlWriter中使用MemoryStream

 1         public static void UseMemoryStreamInXmlWriter()
 2         {
 3             var ms = new MemoryStream();
 4             using (ms)
 5             {
 6                 //定义一个XmlWriter
 7                 using (XmlWriter writer= XmlWriter.Create(ms))
 8                 {
 9                     //写入xml头
10                     writer.WriteStartDocument(true);
11                     //写入一个元素
12                     writer.WriteStartElement("Content");
13                     //为这个元素增加一个test属性
14                     writer.WriteStartAttribute("test");
15                     //设置test属性的值
16                     writer.WriteValue("萌萌小魔王");
17                     //释放缓冲,这里可以不用释放,但是在实际项目中可能要考虑部分释放对性能带来的提升
18                     writer.Flush();
19                     Console.WriteLine($"此时内存使用量为:{GC.GetTotalMemory(false)/1024}KB,该MemoryStream已使用容量为:{Math.Round((double)ms.Length,4)}byte,默认容量为:{ms.Capacity}byte");
20                     Console.WriteLine($"重新定位前MemoryStream所在的位置是{ms.Position}");
21                     //将流中所在当前位置往后移动7位,相当于空格
22                     ms.Seek(7, SeekOrigin.Current);
23                     Console.WriteLine($"重新定位后MemoryStream所存在的位置是{ms.Position}");
24                     //如果将流所在的位置设置位如下示的位置,则XML文件会被打乱
25                     //ms.Position = 0;
26                     writer.WriteStartElement("Content2");
27                     writer.WriteStartAttribute("testInner");
28                     writer.WriteValue("萌萌小魔王2");
29                     writer.WriteEndElement();
30                     writer.WriteEndElement();
31                     //再次释放
32                     writer.Flush();
33                     Console.WriteLine($"此时内存使用量为:{GC.GetTotalMemory(false) / 1024}KB,该MemoryStream已使用容量为:{Math.Round((double)ms.Length, 4)}byte,默认容量为:{ms.Capacity}byte");
34                     //建立一个FileStream 文件创建目的地是f:\test.xml
35                     var fs=new FileStream(@"f:\test.xml",FileMode.OpenOrCreate);
36                     using (fs)
37                     {
38                         //将内存流注入FileStream
39                         ms.WriteTo(fs);
40                         if (ms.CanWrite)
41                         {
42                             //释放缓冲区
43                             fs.Flush();
44                         }
45                     }
46                     Console.WriteLine();
47                 }
48             }
49         }

运行结果:

咱看一下XML文本是什么样的?

 2、自定义处理图片的HttpHandler

有时项目里我们必须将图片进行一定的操作,例如:水印,下载等,为了方便和管理我们可以自定义一个HttpHandler来负责这些工作

后台代码:

 1     public class ImageHandler : IHttpHandler
 2     {
 3         /// <summary>
 4         /// 实现IHttpHandler接口中ProcessRequest方法
 5         /// </summary>
 6         /// <param name="context"></param>
 7         public void ProcessRequest(HttpContext context)
 8         {
 9             context.Response.Clear();
10             //得到图片名
11             var imageName = context.Request["ImageName"] ?? "小魔王";
12             //得到图片地址
13             var stringFilePath = context.Server.MapPath($"~/Image/{imageName}.jpg");
14             //声明一个FileStream用来将图片暂时放入流中
15             FileStream stream=new FileStream(stringFilePath,FileMode.Open);
16             using (stream)
17             {
18                 //通过GetImageFromStream方法将图片放入Byte数组中
19                 var imageBytes = GetImageFromStream(stream, context);
20                 //上下文确定写道客户端时的文件类型
21                 context.Response.ContentType = "image/jpeg";
22                 //上下文将imageBytes中的数组写到前端
23                 context.Response.BinaryWrite(imageBytes);
24             }
25         }
26
27         public bool IsReusable => true;
28
29         /// <summary>
30         /// 将流中的图片信息放入byte数组后返回该数组
31         /// </summary>
32         /// <param name="stream">文件流</param>
33         /// <param name="context">上下文</param>
34         /// <returns></returns>
35         private byte[] GetImageFromStream(FileStream stream, HttpContext context)
36         {
37             //通过Stream到Image
38             var image = Image.FromStream(stream);
39             //加上水印
40             image = SetWaterImage(image, context);
41             //得到一个ms对象
42             MemoryStream ms = new MemoryStream();
43             using (ms)
44             {
45                 //将图片保存至内存流
46                 image.Save(ms,ImageFormat.Jpeg);
47                 byte[] imageBytes = new byte[ms.Length];
48                 ms.Position = 0;
49                 //通过内存流放到imageBytes
50                 ms.Read(imageBytes, 0, imageBytes.Length);
51                 //ms.Close();
52                 //返回imageBytes
53                 return imageBytes;
54             }
55         }
56
57         private Image SetWaterImage(Image image, HttpContext context)
58         {
59             Graphics graphics = Graphics.FromImage(image);
60             Image waterImage = Image.FromFile(context.Server.MapPath("~/Image/logo.jpg"));
61             graphics.DrawImage(waterImage, new Point { X = image.Size.Width - waterImage.Size.Width, Y = image.Size.Height - waterImage.Size.Height });
62             return image;
63         }
64     }

别忘了,还要再web.config中进行配置,如下:

这样前台就能使用了

让我们来看一下输出结果:

哈哈,还不错。

好了,MemoryStream相关的知识就先分享到这里了。同志们,再见!

原文地址:https://www.cnblogs.com/xiaomowang/p/11886132.html

时间: 2024-11-06 10:57:44

MemoryStream相关知识分享的相关文章

测试相关知识分享(百度和其他途径查找到的)

A: 1.一套完整的测试应该由哪些阶段组成? 参考答案:测试计划.测试设计与开发.测试实施.测试评审与测试结论 2.单元测试的主要内容? 参考答案: 模块接口测试.局部数据结构测试.路径测试.错误处理测试.边界测试 3.集成测试也叫组装测试或者联合测试,请简述集成测试的主要内容? 参考答案: (1)在把各个模块连接起来的时候,穿越模块接口的数据是否会丢失; (2)一个模块的功能是否会对另一个模块的功能产生不利的影响; (3)各个子功能组合起来,能否达到预期要求的父功能; (4)全局数据结构是否有

XML的相关基础知识分享(二)

前面我们讲了一下XML相关的基础知识(一),下面我们在加深一下,看一下XML高级方面. 一.命名空间 1.命名冲突 XML命名空间提供避免元素冲突的方法. 命名冲突:在XML中,元素名称是由开发者定义的,当两个不同的文档使用相同的原俗名时,就会发生命名冲突.例如:下面这个XML文档携带者某个表格中的信息: 1 <table> 2 <tr> 3 <td>Apples</td> 4 <td>Bananas</td> 5 </tr&g

深入浅出安卓学习相关知识,如何从零学好移动开发

原文发表自我的个人主页,欢迎大家访问 http://purplesword.info/mobile-develop 由于近几年来互联网的飞速发展,安卓和iOS平台的大量普及推广,移动开发在当前是非常热门的一个方向. 有不少同学问我如何学习安卓,要学些什么,难不难学.之前一直没有想好应该怎么回答这个问题,只是简单的说安卓自身门槛不高,并不难学.因为我觉得准确回答一个类似这样的问题往往需要灵感.现在根据我的学习体验,做个大概的总结. 1.我为什么学安卓 我从刚开始接触安卓开发到现在也有两三年的时间了

(整理)ubuntu 的 相关知识(来自 鸟哥的私房菜)

1. Linux 文件权限概念 $ ls 察看文件的指令 $ ls -al 出所有的文件详细的权限与属性 (包含隐藏档,就是文件名第一个字符为『 . 』的文件) 在你第一次以root身份登入Linux时, 如果你输入上述指令后,应该有上列的几个东西,先解释一下上面七个字段个别的意思: 图2.1.1.文件属性的示意图 第一栏代表这个文件的类型与权限(permission): 这个地方最需要注意了!仔细看的话,你应该可以发现这一栏其实共有十个字符:(图2.1.1及图2.1.2内的权限并无关系) 图2

用户体验报告——知识分享

一.知乎 1.目标用户群:脑子里有经验.价值.知识的人:想要得到别人脑子里的经验.价值.知识的人 2.该产品解决了用户哪些需求? (1)获取某一领域专业大咖的知识.解惑.经验 (2)知识分享.变现 (3)社交 3.该产品用怎样的流程.逻辑实现用户需求? (1)用户针对某一领域进行提问,邀请该领域专业大V进行解答,大V接受邀请评论区回复,其他用户同样可以不请自来在评论区回复 (2)回复质量高,或者具有一定知名度的专业人员可以在知乎开设"付费提问"."Live".专栏,

【ALearning】第二章 Android工程相关知识介绍

本章主要初步介绍Android工程开发环境的搭建,以对Android项目整体的认识与了解.本章包括Android开发环境搭建.第一个Android项目Hello World与Android项目的文件目录结构介绍. Android开发环境搭建 Android开发环境的搭建方式有两种,分别是Eclipse/MyEclipse+ADT+Android SDK和Android Developer Tools.前者方式的开发环境搭建,参看[http://blog.sina.com.cn/s/blog_4e

HTTP协议知多少-关于http1.x、http2、SPDY的相关知识

作为网站开发的基础协议,我们知道浏览器上都有输出http这四个字母,这意味着什么呢? 这就是最基础的HTTP协议. 逐浪君今天为各位大人准备了一些HTTP技术的知识,来和大家分享. 以下图为例: 这一个加载界面,至少用到了http/1.1.SPDY.Http/2(简称h2)三种浏览器技术. 先说HTTP/1.1: HTTP1.1(Hypertext Transfer Protocol Version 1.1)超文本传输协议-版本1.1它是用来在Internet上传送超文本的传送协议.它是运行在T

SCOM 2012知识分享-25:命令行代理安装

适应平台:System Center 2012 RTM/SP1 ------------------------------------------------------------------------------------------------------ 你可以通过命令行使用 MOMAgent.msi 部署 System Center 2012 – Operations Manager 代理. 通过命令行部署代理也称为手动安装. 开始部署之前,请确保满足以下条件: 用来运行 MOM

linux 创建守护进程的相关知识

linux 创建守护进程的相关知识 http://www.114390.com/article/46410.htm linux 创建守护进程的相关知识,这篇文章主要介绍了linux 创建守护进程的相关知识,需要的朋友可以参考下 关键字:linux.守护进程 创建子进程,父进程退出 这是编写守护进程的第一步.由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象.之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到