当心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. Java注解全面解析(转)

    1.基本语法 注解定义看起来很像接口的定义.事实上,与其他任何接口一样,注解也将会编译成class文件. @Target(ElementType.Method) @Retention(Retentio ...

  2. R12 付款过程请求-功能和技术信息 (文档 ID 1537521.1)

    In this Document   Abstract   History   Details   _afrLoop=2234450430619177&id=1537521.1&dis ...

  3. hdu 4685 Prince and Princess(匈牙利算法 连通分量)

    看了别人的题解.须要用到匈牙利算法的强连通算法 #include<cstdio> #include<algorithm> #include<vector> #pra ...

  4. TCP/IP协议栈源码图解分析系列10:linux内核协议栈中对于socket相关API的实现

    题记:本系列文章的目的是抛开书本从Linux内核源代码的角度详细分析TCP/IP协议栈内核相关技术 轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.com linu ...

  5. ThinkPHP中的volist标签中使用eq标签出错

    参考地址:http://blog.csdn.net/luquansen/article/details/18310855 源码: <volist id="v" name=&q ...

  6. openwrt教程 第一章 物联网&amp;openwrt开发概述

    1.1 我们的宗旨 互联网.移动互联网的时代已经过去,物联网的时代已经来临!2014年,是物联网元年,2016年,物联网将达到高潮!为了迎接该潮流,我们工作室(F403科技创意室:http://f40 ...

  7. js正则匹配html内容

    1.得到网页上的链接地址: string matchString = @"<a[^>]+href=\s*(?:'(?<href>[^']+)'|"" ...

  8. 关于java mail 发邮件的问题总结(转)

    今天项目中有需要用到java mail发送邮件的功能,在网上找到相关代码,代码如下: import java.io.IOException; import java.util.Properties; ...

  9. Phpcms所有系统变量列表 Phpcms V9 文件目录结构

    Phpcms所有系统变量列表 用户变量: view plaincopy to clipboardprint? $_userid    用户id   $_username 用户名   $_areaid  ...

  10. abstract修饰方法总结

    abstract这种方法修饰,主要用在抽象类和抽象方法. 抽象的类是不可实例化的比如 public abstract class Test{ } 他能够含有抽象的方法 public abstract ...