一、简单介绍一下MemoryStream

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

二、MemoryStream和FileStream的区别

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

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

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

             //测试byte数组 假设该数组容量是256M
var testBytes = new byte[ * * ];
var ms = new MemoryStream();
using (ms)
{
for (int i = ; i < ; i++)
{
try
{
ms.Write(testBytes, , testBytes.Length);
}
catch
{
Console.WriteLine("该内存流已经使用了{0}M容量的内存,该内存流最大容量为{1}M,溢出时容量为{2}M",
GC.GetTotalMemory(false) / ( * ),//MemoryStream已经消耗内存量
ms.Capacity / ( * ), //MemoryStream最大的可用容量
ms.Length / ( * ));//MemoryStream当前流的长度(容量)
break;
}
}
}
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

         public static void UseMemoryStreamInXmlWriter()
{
var 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($"此时内存使用量为:{GC.GetTotalMemory(false)/1024}KB,该MemoryStream已使用容量为:{Math.Round((double)ms.Length,4)}byte,默认容量为:{ms.Capacity}byte");
Console.WriteLine($"重新定位前MemoryStream所在的位置是{ms.Position}");
//将流中所在当前位置往后移动7位,相当于空格
ms.Seek(, SeekOrigin.Current);
Console.WriteLine($"重新定位后MemoryStream所存在的位置是{ms.Position}");
//如果将流所在的位置设置位如下示的位置,则XML文件会被打乱
//ms.Position = 0;
writer.WriteStartElement("Content2");
writer.WriteStartAttribute("testInner");
writer.WriteValue("萌萌小魔王2");
writer.WriteEndElement();
writer.WriteEndElement();
//再次释放
writer.Flush();
Console.WriteLine($"此时内存使用量为:{GC.GetTotalMemory(false) / 1024}KB,该MemoryStream已使用容量为:{Math.Round((double)ms.Length, 4)}byte,默认容量为:{ms.Capacity}byte");
//建立一个FileStream 文件创建目的地是f:\test.xml
var fs=new FileStream(@"f:\test.xml",FileMode.OpenOrCreate);
using (fs)
{
//将内存流注入FileStream
ms.WriteTo(fs);
if (ms.CanWrite)
{
//释放缓冲区
fs.Flush();
}
}
Console.WriteLine();
}
}
}

运行结果:

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

 2、自定义处理图片的HttpHandler

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

后台代码:

     public class ImageHandler : IHttpHandler
{
/// <summary>
/// 实现IHttpHandler接口中ProcessRequest方法
/// </summary>
/// <param name="context"></param>
public void ProcessRequest(HttpContext context)
{
context.Response.Clear();
//得到图片名
var imageName = context.Request["ImageName"] ?? "小魔王";
//得到图片地址
var stringFilePath = context.Server.MapPath($"~/Image/{imageName}.jpg");
//声明一个FileStream用来将图片暂时放入流中
FileStream stream=new FileStream(stringFilePath,FileMode.Open);
using (stream)
{
//通过GetImageFromStream方法将图片放入Byte数组中
var imageBytes = GetImageFromStream(stream, context);
//上下文确定写道客户端时的文件类型
context.Response.ContentType = "image/jpeg";
//上下文将imageBytes中的数组写到前端
context.Response.BinaryWrite(imageBytes);
}
} public bool IsReusable => true; /// <summary>
/// 将流中的图片信息放入byte数组后返回该数组
/// </summary>
/// <param name="stream">文件流</param>
/// <param name="context">上下文</param>
/// <returns></returns>
private byte[] GetImageFromStream(FileStream stream, HttpContext context)
{
//通过Stream到Image
var 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 = ;
//通过内存流放到imageBytes
ms.Read(imageBytes, , imageBytes.Length);
//ms.Close();
//返回imageBytes
return imageBytes;
}
} private Image SetWaterImage(Image image, HttpContext context)
{
Graphics graphics = Graphics.FromImage(image);
Image waterImage = Image.FromFile(context.Server.MapPath("~/Image/logo.jpg"));
graphics.DrawImage(waterImage, new Point { X = image.Size.Width - waterImage.Size.Width, Y = image.Size.Height - waterImage.Size.Height });
return image;
}
}

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

这样前台就能使用了

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

哈哈,还不错。

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

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

  1. 【Stream—6】BufferedStream相关知识分享

    一.简单介绍以下BufferedStream 在前几章的讲述中,我们已经能够掌握流的基本特性和特点,一般进行对流的处理时,系统肩负着IO所带来的开销,调用十分频繁,这时候就应该想个办法减少这种开销,而 ...

  2. FileStream相关知识分享

    一.如何理解FIleStream 通过前3章的学些,相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图: 我们磁盘中的任何文件都是通过二进制数组组成,最为直观的 ...

  3. 【Stream—7】NetworkStream相关知识分享

    一.NetworkStream的作用 和先前的流有所不同,NetworkStream的特殊性可以在它的命名空间中得以了解(System.Net.Sockets),聪明的你马上就会反应过来:既然是在网络 ...

  4. StreamWriter 相关知识分享

    在介绍StreamWriter之前,我们首先来了解一下它的父类TextWriter. 一.TextWriter 1.TextWriter的构造函数和常用属性方法 下面是TextWriter的构造函数: ...

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

    前面我们讲了一下XML相关的基础知识(一),下面我们在加深一下,看一下XML高级方面. 一.命名空间 1.命名冲突 XML命名空间提供避免元素冲突的方法. 命名冲突:在XML中,元素名称是由开发者定义 ...

  6. XML的相关基础知识分享

    XML和Json是两种最常用的在网络中数据传输的数据序列化格式,随着时代的变迁,XML序列化用于网络传输也逐渐被Json取代,前几天,单位系统集成开发对接接口时,发现大部分都用的WebService技 ...

  7. listener监听器的相关知识

    从别人的博客上我学习了listener的相关知识现在分享给大家 1.概念: 监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上 ...

  8. 分享吉林大学机械科学与工程学院,zhao jun 博士的Halcon学习过程及知识分享

    分享吉林大学机械科学与工程学院,zhao jun 博士的Halcon学习过程及知识分享 全文转载zhao jun 博士的新浪博客,版权为zhaojun博士所有 原文地址:http://blog.sin ...

  9. JS作用域相关知识(#精)

    在学习<你不知道的JS>一书中,特将作用域相关知识在此分享一下: #说到作用域,就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值,则使用LHS查询 2)如果查询目的是 ...

随机推荐

  1. 自学maya三月,为啥还是95%都还不会,那是因为你不懂这几个技巧

    有一些学员经常会有这种疑问,为什么学习MAYA软件这么难,为什么自己怎么学都学不会? 结果调查,发现了下面几个问题. 游戏建模 第一: 走弯路 很多人一开始学习Maya的时候肯定第一步是安装软件,但是 ...

  2. C#版ASP.NET Web API使用示例

    为更好更快速的上手Webapi设计模式的接口开发,本文详细解释了在Web API接口的开发过程中,我们可能会碰到各种各样的问题总结了这篇,希望对大家有所帮助. 1:在接口定义中确定MVC的get或者P ...

  3. 数据结构1_java---单链表的操作,约瑟夫问题

    我们经常实用c++来建立链表,为了学习的方便,此处我使用java实现了对链表的增删改查功能 整个过程较为简单.仅供参考 流程: (1)通过内部类Node建立结点,内部变量作为指针域和数据域,并写下构造 ...

  4. linux下mqtt-client

    CPATH += ../embe_mqtt/MQTTClient/srcPSRTPATH = ../embe_mqtt/MQTTPacket/src LOADPATH += -I$(CPATH)LOA ...

  5. MyCat教程五:实现分库分表

      本文我们来介绍下MyCat的分库分表操作 分库分表 一.分片规则介绍   在rule.xml中定义了各种myCat支持的分片规则. 取模mod-long 自然月分片 sharding-by-mon ...

  6. Python爬取散文网散文

    配置python 2.7 bs4 requests 安装 用pip进行安装 sudo pip install bs4 sudo pip install requests 简要说明一下bs4的使用因为是 ...

  7. Unity - HasExitTime用法

    本文详细分析了AnimatorController中动画切换过渡问题,即Translation过渡及hasExitTime的问题.方法为对实际项目中的所有情况进行分类,规划逻辑图,可视化分析解决这些问 ...

  8. 实战SpringCloud响应式微服务系列教程(第八章)构建响应式RESTful服务

    本文为实战SpringCloud响应式微服务系列教程第八章,讲解构建响应式RESTful服务.建议没有之前基础的童鞋,先看之前的章节,章节目录放在文末. 1.使用springboot2.1.4构建RE ...

  9. 解决IDEA下SpringBoot启动没有Run Dashboard并找回

    前两天看到别人SpringBoot启动服务,启动器是长这样的 而我的呢?是这样的 Run Dashboard 它是一个代替Run窗口的一个更好清晰简洁的一个启动器. 如果我们需要启动多个窗口时,Run ...

  10. 2018.8.13 python中生成器和生成器表达式

    主要内容: 1.生成器和生成器函数 2.列表推导式 一.生成器 生成器是指就是迭代器,在python中有三种方式来获取生成器: 1.通过生成器函数 2.通过各种推导式来实现生成器 3.通过数据的转换也 ...