MemoryStream相关知识分享
一、简单介绍一下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相关知识分享的更多相关文章
- 【Stream—6】BufferedStream相关知识分享
一.简单介绍以下BufferedStream 在前几章的讲述中,我们已经能够掌握流的基本特性和特点,一般进行对流的处理时,系统肩负着IO所带来的开销,调用十分频繁,这时候就应该想个办法减少这种开销,而 ...
- FileStream相关知识分享
一.如何理解FIleStream 通过前3章的学些,相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图: 我们磁盘中的任何文件都是通过二进制数组组成,最为直观的 ...
- 【Stream—7】NetworkStream相关知识分享
一.NetworkStream的作用 和先前的流有所不同,NetworkStream的特殊性可以在它的命名空间中得以了解(System.Net.Sockets),聪明的你马上就会反应过来:既然是在网络 ...
- StreamWriter 相关知识分享
在介绍StreamWriter之前,我们首先来了解一下它的父类TextWriter. 一.TextWriter 1.TextWriter的构造函数和常用属性方法 下面是TextWriter的构造函数: ...
- XML的相关基础知识分享(二)
前面我们讲了一下XML相关的基础知识(一),下面我们在加深一下,看一下XML高级方面. 一.命名空间 1.命名冲突 XML命名空间提供避免元素冲突的方法. 命名冲突:在XML中,元素名称是由开发者定义 ...
- XML的相关基础知识分享
XML和Json是两种最常用的在网络中数据传输的数据序列化格式,随着时代的变迁,XML序列化用于网络传输也逐渐被Json取代,前几天,单位系统集成开发对接接口时,发现大部分都用的WebService技 ...
- listener监听器的相关知识
从别人的博客上我学习了listener的相关知识现在分享给大家 1.概念: 监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上 ...
- 分享吉林大学机械科学与工程学院,zhao jun 博士的Halcon学习过程及知识分享
分享吉林大学机械科学与工程学院,zhao jun 博士的Halcon学习过程及知识分享 全文转载zhao jun 博士的新浪博客,版权为zhaojun博士所有 原文地址:http://blog.sin ...
- JS作用域相关知识(#精)
在学习<你不知道的JS>一书中,特将作用域相关知识在此分享一下: #说到作用域,就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值,则使用LHS查询 2)如果查询目的是 ...
随机推荐
- [USACO10NOV]奶牛的图片Cow Photographs
题目描述 Farmer John希望给他的N(1<=N<=100,000)只奶牛拍照片,这样他就可以向他的朋友炫耀他的奶牛. 这N只奶牛被标号为1..N. 在照相的那一天,奶牛们排成了一排 ...
- 研究了3天,终于将 Shader 移植到 Cocos Creator 2.2.0 上了!
预览 扫光特效-Fluxay2 马赛克像素特效-Mosaic 过渡效果-Transfer Shawn 花了3天时间,研究了Cocos Creator 2.2.0 的 Effect 语法,终于在1024 ...
- 浏览器标签tab窗口切换时事件状态侦听
做到 是大屏项目,用的websocket,在浏览器切换标签窗口后,过了一段时间回来,页面会非常卡,所以想页面切回来的时候刷新页面,找到了这个方法,这是原来的例子.这段代码可以自己复制去做下测试 var ...
- 简单,常用,基础的css滤镜效果!
第一次写博客,有些紧张.如写的不好,尽请谅解! 2019-10-28 09:33:48 第一:透明度滤镜 该滤镜的效果其实跟调整透明度差不多.它总共有7个参数!语法如下!(摈弃那些花里胡哨,我们只 ...
- 七、springBoot 简单优雅是实现文件上传和下载
前言 好久没有更新spring Boot 这个项目了.最近看了一下docker 的知识,后期打算将spring boot 和docker 结合起来.刚好最近有一个上传文件的工作呢,刚好就想起这个脚手架 ...
- 阿里巴巴开源故障注入工具_chaosblade
chaosblade是阿里巴巴最近开源的一款故障注入的工具,因为我最近在做公司的虚拟化平台的可靠性测试工具,无意中发现这个工具,个人感觉比较有用,用起来也比较简单,所以拿出来分享一下,期望对大家的工作 ...
- JAVA NIO udp 实现 群转发
场景很简单,就是多个客户端通过udp,连接到服务器(其实是无连接的,就是服务器保存了客户端的ip信息).然后通过udp协议先服务器发送消息,然后服务器在通过udp转发在各个客服端. 这个是不是 观察者 ...
- Redux的核心概念,实现代码与应用示例
Redux是一种JavaScript的状态管理容器,是一个独立的状态管理库,可配合其它框架使用,比如React.引入Redux主要为了使JavaScript中数据管理的方便,易追踪,避免在大型的Jav ...
- 修改List<Map<String, Object>>的值
List<Map<String, Object>> aList = new ArrayList(); //加入一个Map元素Map map = new HashMap();m ...
- Flask:数据库的操作
1.对数据库的增加操作 在Django中,数据库查询需要借助objects方法,在Flask中也有类似的操作.在执行对数据库的增加操作之前,我们首先需要实例化一个session对象,这里的sessio ...