一、简单介绍一下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. 详解立即执行函数(function(){}()),(function(){})()

    要知道这几种写法之间的区别,我们要先聊些题外话——js中函数的两种命名方式,即表达式和声明式. 函数的声明式写法为:function foo(){/*...*/},这种写法会导致函数提升,所有func ...

  2. 基于Spring Boot的统一异常处理设计

    基于Spring Boot的统一异常处理设计 作者: Grey 原文地址:https://www.cnblogs.com/greyzeng/p/11733327.html Spring Boot中,支 ...

  3. 记因git规范导致的提测和发布延迟

    号外 最近因为换工作的原因,我的博客和Github没有像之前那样频繁的更新了.一方面原因是投递简历和准备面试,由于之前的基础没有很扎实,需要把平时的知识点都整理一遍.这个时间段持续了20多天的样子,因 ...

  4. Java 异常处理的 20 个最佳实践,你知道几个?

    异常处理是 Java 开发中的一个重要部分,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java 提供了几个异常处理特性,以try,catch 和 finally 关键字的形式内建 ...

  5. codeforce - 13A A.Numbers

    A. Numbers time limit per test 1 second memory limit per test 64 megabytes input standard input outp ...

  6. Nginx在Window上简单的使用

    先上Nginx在Window上的基本常用指令: IP_hase也可以解决Session共享的问题:不过不推荐这样使用,建议使用 Memcache/redis来处理 session共享的问题 轮询还是权 ...

  7. 设计模式C++描述----04.观察者(Observer)模式

    一. 概述 Observer 模式要解决的问题为:建立一个一(Subject)对多(Observer)的依赖关系,并且做到当“一”变化的时候,依赖这个“一”的多也能够同步改变. Sbuject 相当于 ...

  8. 我是如何在一周内拿到4份offer的?

    前言 大概一个月没写博客了吧,这段时间事情比较多(家里有事,请了一段时间假,正好利用剩余几天时间面了几次试),也没抽出来时间写博客,还好所有的事情已经处理完了,今天闲来无事就整理一下这几次面试过程中遇 ...

  9. 【EmguCV视频教程】VS2017+EmguCV3.4(C# OpenCV)高清入门视频教程

    视频采用VS2017 + EmguCV3.4版本录制,内容类似本人的Python和C++版本,如果需要的朋友可加我咨询,视频共40讲,从按照到读取显示图片,图形预处理,边缘检测,形态学,角点检测,轮廓 ...

  10. Web for pentester_writeup之File Upload篇

    Web for pentester_writeup之File Upload篇 File Upload(文件上传) Example 1 直接上传一句话木马,使用蚁剑连接 成功连接,获取网站根目录 Exa ...