一种MemoryStream的替代方案

简介 这篇文章阐述了当使用MemoryStream处理大型数据集时经常触发的模棱两可的OutofMemoryException异常,并且介绍了一个类——MemoryTributary,他可以用来替代.NET内置的MemoryStream,并且能够支持大型数据的处理。 背景 当试图使用MemoryStream处理较大数据(in the order of tens of MB)时,它通常会引发OutofMemoryException异常。这是不是因为,正如其命名的那样,超出了系统内存的限制了呢?但实际上那都是进程的虚拟地址空间。 当进程从Windows申请内存的时候,内存管理器并没有从RAM中分配地址空间,但是“页面”——存储块(通常是4KB)可以存在于RAM、磁盘或者任何内存管理器能够决定存储它们的地方。页面映射到进程的地址空间,因此,假如当进程试图从[0xAF758000]开始访问内存时,它实际上是在访问[第496页]开始的地方,无论是否恰好是[第496页]。可以为进程分配任何大小的足够的内存,只要磁盘空间充足,并可以将其中的一大部分作为虚拟地址空间,这适用于任何时候。在这个分配过程中会产生大量的小碎片(这里是指存储块)。 这是因为进程地址空间是支离破碎的:大部分都被操作系统占用,诸如应用程序映像、库以及其他预分配的空间等。一旦内存管理器分配了所请求的相当于页面大小的内存,就必须在进程内映射到(虚拟)地址空间。而当剩余的地址空间中没有足够的连续空间时,页面就无法被映射,从而导致内存分配失败,也就引发了OutofMemoryException异常。 这个过程并没有耗尽空间和地址,它运行在连续的地址上。要看到这个(如果你在用64位系统),在x86目标平台下编译如下程序并运行它,然后再编译为x64目标平台,看看两者的区别。 1 static void Main(string[] args) 2 { 3 List allocations = new List(); 4 5 for (int i = 0; true; i++) 6 { 7 try 8 { 9 allocations.Add(new byte[i * i * 10]); 10 } 11 catch (OutOfMemoryException e) 12 { 13 Console.Write(string.Format("Performed {0} allocations",i)); 14 } 15 } 16 } 复制代码 .NET当前实现的MemoryStream使用了一个字节数组作为后备存储。当要写入的内容超出数组长度的时候,容量就会自动扩展一倍(老陈注:这是.NET数组自身的内存分配机制)。根据程序这种行为,基于这种后备存储机制的MemoryStream会很快就需要比可用虚拟地址空间更多的内存。 代码用例 这个解决方案并不是申请更多更大的连续内存去存储流中包含的数据。MemoryTributary内部使用了4KB的动态集合来作为后备存储,实现按需分配。 MemoryTributary从Stream派生,因此可以像使用其他Stream一样的使用它,类似于MemoryStream。 MemoryTributary不过是想作为MemoryStream的一个替代方案,但不是可以在任何情况下都可以替代MemoryStream的,以下是一些注意事项: MemoryTributary没有完全实现MemoryStream所有的构造函数(因为MemoryTributary的容量是没有人为限制的)。MemoryTributary的初始容量从一个byte[]开始。 MemoryTributary是Stream的子类,而不是MemoryStream,所以不能用在仅接受MemoryStream成员的地方。 MemoryTriburary没有后备存储实现GetBuffer(),类似功能的方法是ToArray(),但是要慎用! 使用MemoryTributary要注意以下几点: 块是在访问时按需分配的(例如在读取或写入时)。在读取之前,重新检查Position,以确保读取操作是在流范围内进行的。 1 // 创建 MemoryTributary 的新实例:此时Length为0,Position为0,没有内存被分配 2 MemoryTributary d = new MemoryTributary(); 3 4 // 因为Length为0所以返回-1,没有内存被分配 5 int a = d.ReadByte(); 6 7 // Length现在设定为10000字节,但是没有内存被分配! 8 d.SetLength(10000); 9 10 // 现在有3个内存块被分配了, 11 // 但是b是未定义的,因为它们还没有完成初始化 12 int b = d.ReadByte(); 复制代码 内存是分配在连续的块上的,也就是说,如果第一个块是块3的话,那么块1和块2也会被自动分配掉; MemoryTributary包含了一个ToArray()方法,但这是不安全的; 作为替代,MemoryTributary使用ReadFrom()和WriteTo()这两个方法对大数据进行操作。 性能指标 在容量和速度上,MemoryStream和MemoryTributary都是很难预料的,因为这依赖于很多因素。一个很重要的因素是当前进程的内存碎片——一个进程分配了大量内存将会导致大量内存碎片的发生。 容量 流 平均崩溃临界值(MB) MemoryStream 488 MemoryTributary 1272 速度(访问时间) 流测试执行时间比对 (ms) 读写容量 (MB) MemoryStream MemoryTributary (4KB Block) MemoryTributary (64KB Block) MemoryTributary (1MB Block) 10 10 13 11 7 3 5 3 3 3 6 3 3 3 5 3 3 4 5 3 3 3 6 3 3 100 100 148 123 52 34 54 42 35 34 48 35 34 35 47 36 35 34 48 36 35 35 51 35 35 500 516 390 290 237 167 222 184 170 168 186 154 167 167 187 151 168 167 186 151 168 167 185 153 168 1,000 1185 1585 1299 485 347 547 431 344 343 463 350 345 338 462 350 345 3377 461 349 345 339 465 351 343 代码下载 请到原文地址下载:http://www.codeproject.com/Articles/348590/A-replacement-for-MemoryStream NoSQL系列技术QQ群: 一群:23152359(满员) 二群:193713524(强烈推荐) 三群:132740383(实名群,仅邀请加入) 四群:191830739(精英群,仅邀请加入) JavaScript中的匿名函数、回调函数、自调用函数 03 2012 档案 一种MemoryStream的替代方案 摘要: 这篇文章阐述了当使用MemoryStream处理大型数据集时经常触发的模棱两可的OutofMemoryException异常,并且介绍了一个类——MemoryTributary,他可以用来替代.NET内置的MemoryStream,并且能够支持大型数据的处理。阅读全文 posted @ 2012-03-25 10:12 陈彦铭 阅读(1071) | 评论 (8) 编辑 创业老板不能犯的十种错误 摘要: 1.哥们式合伙,仇人式散伙 2.盲目崇拜社会关系 3.迷信“空降兵” 4.企业任人唯亲 5.面子问题导致“一言堂” 6.商业迷信 7.知人而不自知 8.习惯性信用缺失 9.土匪式的企业文化 10.企业进行阶级斗争化阅读全文 posted @ 2012-03-24 23:51 陈彦铭 阅读(57) | 评论 (0) 编辑 C#获取命令行输出内容的方法 摘要: 很多时候我们需要以编程的方式获取命令行输出的内容,研究了不少时间,终于搞定了。获取命令行输出内容的方式有传统和异步两种方式。阅读全文 posted @ 2012-03-23 23:53 陈彦铭 阅读(665) | 评论 (3) 编辑 .NET枚举类型优化探讨(三) 摘要: 在.NET枚举类型优化探讨(二)中我们探讨了“使用类或结构来替代部分枚举类型”的方案并试图进行进一步的重构和优化,但是发现有很多限制,不但没有完成重构,且发现了很多该方案不适用的地方和缺陷。在某些情况下,这种方案会对生产带来相反的作用,所以在文中我建议不要滥用。今天我们来探讨一下使用.NET中的Attitude特性来扩展.NET枚举值的方案。阅读全文 posted @ 2012-03-22 12:05 陈彦铭 阅读(1079) | 评论 (0) 编辑 .NET枚举类型优化探讨(二) 摘要: 昨天在.NET中的枚举值(一)中我提到,如果将该文中的实现进一步架构,提炼出一个抽象类作为自定义枚举类型的基类的话,肯定会对后续开发有很好的帮助。但实际上,老陈犯下了一个严重的错误……阅读全文 posted @ 2012-03-21 11:48 陈彦铭 阅读(104) | 评论 (0) 编辑 .NET枚举类型优化探讨(一) 摘要: 昨天晚上通过博文《Java中的枚举值》和大家分享探讨了Java枚举值语法的非常规性和它给力的地方,该文引起了.NET猴子的一些非议,因为Java能做到的,.NET基本上也能做到。那么今天老陈就来和大家共同研究一下.NET中的枚举类型,看看它和Java相比有没有神马优势。阅读全文 posted @ 2012-03-20 11:02 陈彦铭 阅读(321) | 评论 (2) 编辑 Java中的枚举值 摘要: 在C++时代就有了枚举值这个类型,它是一种有序键值对的集合,使用枚举类型可以在语义化和结构化之间达成一种平衡。如果我们的代码中到处都是需要文档才能看懂的数字或字符(串)定义的话,那将是一种非常痛苦的事情。老陈最近在学习Java,在对项目重构的时候,就需要枚举类型来优化代码结构,给力的是,Java和.NET等语言(环境)一样都提供了对枚举类型的直接支持!但是,我却遇到了一个比较苦恼的问题……阅读全文 posted @ 2012-03-19 20:44 陈彦铭 阅读(145) | 评论 (0) 编辑 失败已是过去,现在成就未来 摘要: 2012年3月18日,预订宝正式宣布终止运营。作为联合创始人之一,此时此刻,我的内心并没有很复杂的情绪。因为之前我们已经纠结了很长一段时间…… 这是连续五年的创业经历,中途停顿过,但没有放弃过。坚持,继续坚持,顶着各种压力一再坚持!阅读全文 posted @ 2012-03-18 21:55 陈彦铭 阅读(109) | 评论 (3) 编辑
原文地址:https://www.cnblogs.com/Leo_wl/p/2417199.html