当心Dictionary带来的一种隐式内存泄漏

最近在看Dictionary的源代码的时候, 突然想到Dictionary的不当使用中有一种隐含内存泄漏的可能.

简化使用场景

小A正在写一个简单的图书销售系统.

他首先需要处理的是订单和订单里面对应的书目集合. 接着他发现自己需要一个特定的内存结构, 来临时保存所有的订单及其伴随的销售书目集合, 以减小对数据库的压力. 小A想到了词典Dictionary这个保存关联数据最好用的结构 - 将订单Order对象做为键, 将对应的销售书目Books作为值, 保存在词典中.

订单中包含订单ID/订货人ID/订货时间. 小A知道, 要想将Order对象作为键, 他必须重写Order类的GetHashCode()方法和Equals()方法, 使这两个函数有意义而不是接受系统默认的实现, 这是Dictionary所要求的. 这个功能实现示意如下:

Order Class
  1. internal class Order
  2. {
  3. public int ID { get; set; }
  4. public int PatronID { get; set; }
  5. public DateTime BoughtTime { get; set; }
  6. // ...
  7. public override bool Equals(object obj)
  8. {
  9. if (obj == null)
  10. {
  11. return false;
  12. }
  13. Order orderToCompare = obj as Order;
  14. if (orderToCompare == null)
  15. {
  16. return false;
  17. }
  18. return ID == orderToCompare.ID &&
  19. PatronID == orderToCompare.PatronID &&
  20. BoughtTime == orderToCompare.BoughtTime;
  21. }
  22. public override int GetHashCode()
  23. {
  24. return ("ID" + ID.ToString() +
  25. "PatronID" + PatronID.ToString() +
  26. "TimeStamp" + BoughtTime.ToString())
  27. .GetHashCode();
  28. }
  29. }

后来他发现,对于已经存在的有些订单如果存在用户更改了购买的书籍等操作, 这些订单需要更新, 在更新后需要更新订单的时间戳:

Update Order Property
  1. public void UpdateOrderTime(Order order)
  2. {
  3. order.BoughtTime = DateTime.Now;
  4. }

这个简单的系统写完后刚送去质量部门刚测试了两天, 老板就把小A叫到眼前狠狠剋了一顿, "Memory Leak!"

问题出在哪里呢?

问题出在了作为Dictionary键的Order对象身上.

Dictionary的.NET实现有一个隐含的特性比较容易让人忽略, 那就是它对于存储数据的定位方式. Dictionary是通过对键的哈希值进行散列计算, 从而确定其对应的值存放的位置. 而Dictionary内部的添加/删除/修改操作, 都完全地依赖于这一定位方式. 这个定位方式, 在Dictionary源代码中体现为FindEntry()操作:

  1. private int FindEntry(TKey key) {
  2. if( key == null) {
  3. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
  4. }
  5. if (buckets != null) {
  6. int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
  7. for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
  8. if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
  9. }
  10. }
  11. return -1;
  12. }

当某一个order的BoughtTime属性改变时, 对应的order的哈希值也改变了, 这时伴随该order的书目列表还在Dictionary中,但是FindEntry()操作却没法再定位到它. 这个书目列表将一直存在在Dictionary当中,直到这个Dictionary的生命周期结束. 这就是隐含的内存泄漏. 如果这是个WinForm程序, 或许影响还不是很大. 但是如果出于一个要求高在线率的网络服务当中时, 内存使用Overflow的异常将肯定是不可避免的.

在这个简单场景中体现出来的内存泄漏, 在更为复杂的场景下, 可能会更隐蔽也更难发现.虽然基本的道理是一样的,但是在更复杂的业务逻辑中, 我们可能更容易忽略它的危害.

结论

如果一个业务对象在业务逻辑中可能会被修改, 千万不要将它作为Dictionary的键!!! 使用对象作为Dictionary的键时, 要慎重的考虑这个对象会不会在其余的地方有隐式或者显式地被改变的可能.

恰当的使用Dictionary.

Dictionary带来的一种隐式内存泄漏的更多相关文章

  1. explicit:C++规定,当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换

    explicit研究   explicit是C++中的关键字,不是C语言中的.英文直译是“明确的”.“显式的”意思.出现这个关键字的原因,是在C++中有这样规定的基础上:当定义了只有一个参数的构造函数 ...

  2. 精华阅读第 13 期 |常见的八种导致 APP 内存泄漏的问题

    本期是移动开发精英俱乐部的第13期文章,都是以技术为主,所以这里就不过多的进行赘述了,我们直接看干货内容吧!本文系ITOM管理平台OneAPM整理. 实际项目中的MVVM(积木)模式–序章 导读:开篇 ...

  3. mysql的几种隐式转化

    1. 表定义是字符型,传入的是Int 2. 字符集不一致.表定义的字段是gbk,传入的是utf8:这种在存储过程中出现得比较多. 数据库的字符集utf8 mysql> show create d ...

  4. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

  5. 如何在linux下检测内存泄漏

    之前的文章应用 Valgrind 发现 Linux 程序的内存问题中介绍了利用Linux系统工具valgrind检测内存泄露的简单用法,本文实现了一个检测内存泄露的工具,包括了原理说明以及实现细节. ...

  6. 如何在linux下检测内存泄漏(转)

    本文转自:http://www.ibm.com/developerworks/cn/linux/l-mleak/ 本文针对 linux 下的 C++ 程序的内存泄漏的检测方法及其实现进行探讨.其中包括 ...

  7. [转载]浅谈C/C++内存泄漏及其检测工具

    http://dev.yesky.com/147/2356147_3.shtml 对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题.已经有许多技术被研究出来以应对这个问题,比如Sm ...

  8. Memory Leak(内存泄漏)问题总结(转)

    最近听了一些关于Memory Leak(内存泄漏)的seminar,感觉有些收获,所以留个记录,并share给朋友. 1 什么是Memory Leak. Memory Leak是指由于错误或不完备的代 ...

  9. java中的内存溢出和内存泄漏

    内存溢出:对于整个应用程序来说,JVM内存空间,已经没有多余的空间分配给新的对象.所以就发生内存溢出. 内存泄露:在应用的整个生命周期内,某个对象一直存在,且对象占用的内存空间越来越大,最终导致JVM ...

随机推荐

  1. TCP/IP详细解释--TCP/IP可靠的原则 推拉窗 拥塞窗口

    TCP和UDP在同一水平---传输层.但TCP和UDP最不一样的地方.TCP它提供了一个可靠的数据传输服务,TCP是面向连接的,那.使用TCP两台主机通过第一通信"拨打电话"这个过 ...

  2. 敏捷开发用户故事系列之十一:CSDN博客用户故事分析

    这是敏捷开发用户故事系列的第十一篇.(栏目目录) 经常有人问起有没有完整的用户故事案例.本人在网上找了一下,大约能找到两三篇,但多数只是为了描述用户故事的语法而已,都不涉及用户故事的颗粒度.大量故事的 ...

  3. HTML5线性图表 图表数据区域可着色

    这是一款基于Canvas的HTML5图表应用,在图表数据初始化的时候伴随动画效果. 在线演示: 点击演示 源代码下载: 点击下载 核心jQuery代码: var myData = {   labels ...

  4. VLine2.0——仿阿里巴巴VIPABC真人视频在线教育(基于Flash支持一对多多对多Web在线视频)

    感兴趣的朋友可与我联系:acsebt@qq.com 一.登陆页 二.功能页

  5. 使用RouteDebugger对MVC路由进行调试

    在Asp.Net MVC程序中,路由是MVC程序的入口,每一个Http请求都要经过路由计算,然后匹配到相应的Controller和Action.通常我们的路由都会注册在Global.asax.cs文件 ...

  6. wx_sample.php

    <?php /**   * wechat php test   */ //define your token define("TOKEN", "weixin&quo ...

  7. SocketAsyncEventArgs使用解说

    原文:SocketAsyncEventArgs使用解说 如果在.NET下写过网络通讯的同学应该感觉不陌生了,有很多刚入门的同学很多都认为这东西可以大大提高处理效能还有就是使用上很不适应.其实使用之前最 ...

  8. HTML5游戏开发进阶指南

    <HTML5游戏开发进阶指南> 基本信息 作者: (印)香卡(Shankar,A.R.)    译者: 谢光磊 出版社:电子工业出版社 ISBN:9787121212260 上架时间:20 ...

  9. 09_android入门_採用android-async-http开源项目的GET方式或POST方式实现登陆案例

    依据08_android入门_android-async-http开源项目介绍及用法的介绍,我们通过最常见的登陆案例进行介绍android-async-http开源项目中有关类的使用.希望对你学习an ...

  10. /etc/sysconfig/network-scripts/ifcfg-eth0

    以下各值常见于所有的基本配置文件中:* DEVICE=name,这里name是物理设备的名字(动态分配的PPP设备应当除外,它的名字是“逻辑名”.* IPADDR=addr, 这里addr是IP地址. ...