.net Stream篇(五)

MemoryStreamhtml

目录:c++

1 简单介绍一下MemoryStream程序员

2 MemoryStream和FileStream的区别编程

3 经过部分源码深刻了解下MemoryStreamwindows

4 分析MemorySteam最多见的OutOfMemory异常数组

5 MemoryStream 的构造缓存

6 MemoryStream 的属性多线程

7 MemoryStream 的方法ide

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 overflow
            if (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];
            }
            else
                Buffer.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数组 假设该数组容量是256M
            byte[] 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)
            {
                //定义一个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("此时内存使用量为:{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.xml
                    FileStream fs = new FileStream(@"d:\test.xml",FileMode.OpenOrCreate);
                    using (fs)
                    {
                        //将内存流注入FileStream
                        ms.WriteTo(fs);
                        if(ms.CanWrite)
                          //释放缓冲区
                        fs.Flush();
                    }
                }
            }
        }
复制代码

      输出结果:


简单示例:自定义一个处理图片的HttpHandler

 有时项目里咱们必须将图片进行必定的操做,例如水印,下载等,为了方便和管理咱们能够自定义一个HttpHander 来负责这些工做

后台:

复制代码
  public class ImageHandler : IHttpHandler
    {
        #region IHttpHandler Members

        public 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获得Image
            Image 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;
                //经过内存流读取到imageBytes
                ms.Read(imageBytes, 0, imageBytes.Length);
                ms.Close();
                //返回imageBytes
                return 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,尽请期待!

相关文章
相关标签/搜索