当心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. [半原创]指纹识别+谷歌图片识别技术之C++代码

    原地址:http://blog.csdn.net/guoming0000/article/details/8138223 以前看到一个http://topic.csdn.net/u/20120417/ ...

  2. Exception in thread "main" java.lang.IllegalArgumentException

    1.错误叙述性说明 Exception in thread "main" java.lang.IllegalArgumentException: Cannot format giv ...

  3. Linux 核心阅读工具vim+ctags+cscope+taglist

    今天.介绍vim+ctags+cscope+taglist的内核阅读配置. 当使用过之后,我相信大部分人都会舍弃之前的Eclipse(我就是活生生的一个样例).我们先来看看实现的界面是怎么样的: 我们 ...

  4. C语言中字符串如何转换为二进制、八进制、十进制、十六进制

    在C语言某个程序当中需要把文本16进制转换成对应的16进制数,比如字符串"0x1a"转换成10进制的26,可以用以下函数来实现 相关函数: atof, atoi, atol, st ...

  5. 内存级别/栅栏 ( Memory Barriers / Fences ) – 翻译

    翻译自:Martin Thompson – Memory Barriers/Fences 在这篇文章里,我将讨论并发编程里最基础的技术–以内存关卡或栅栏著称.那让进程内的内存状态对其它进程可见. CP ...

  6. poj3613(恰经过N条边的最短路)

    题目连接:http://poj.org/problem?id=3613 题意:从S 到 T 经过边得个数恰为k的最短路是多少. 分析:01邻接矩阵A的K次方C=A^K,C[i][j]表示i点到j点正好 ...

  7. Android在子线程中更新UI(二)

    MainActivity如下: package cc.testui2; import android.os.Bundle; import android.view.View; import andro ...

  8. 文件操作ofstream,open,close,ifstream,fin,依照行来读取数据, fstream,iosin iosout,fio.seekg(),文件写入和文件读写,文件拷贝和文件

     1.ofstream,open,close 写入文件 #include<iostream> #include<fstream> using namespace std; ...

  9. PHP上传文件超过了最大文件大小限制导致无法上传成功

    最近的研究<HeadFirst PHP & MySQL>第一本书5章"使用存储在文件中的数据",难道当一个文件上传应用程序,发生了错误.即,文件不能成功上传.这 ...

  10. android 防止多次点击,它会导致事件侦听响应于其他接口

    这里有情况: A当点击跳转至B介面,点击B接口结束后,到A界面中 1.此时在B界面中,设置点击事件,点击后结束B v.setOnClickListener(new OnClickListener() ...