惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
博客园_首页
宝玉的分享
宝玉的分享
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
T
Tailwind CSS Blog
雷峰网
雷峰网
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
月光博客
月光博客
罗磊的独立博客
F
Fortinet All Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
阮一峰的网络日志
阮一峰的网络日志
I
InfoQ
Simon Willison's Weblog
Simon Willison's Weblog
D
DataBreaches.Net
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
Scott Helme
Scott Helme
B
Blog
M
MIT News - Artificial intelligence
K
Kaspersky official blog
H
Help Net Security
V
Vulnerabilities – Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
Engineering at Meta
Engineering at Meta
博客园 - 【当耐特】
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
Project Zero
Project Zero
The Hacker News
The Hacker News
B
Blog RSS Feed
T
Tor Project blog

博客园 - Jeffrey Sun

关于Duck Typing的性能分析 - Draft Composite Application Guidance (Prism) V4 Releases! 关于文件流Seek以及Read操作的一点不满 VS2010 "内存不足" 错误补丁 微软Visual Studio 2010 第三集:幸福要敏捷 详解.NET 4.0平行任务库中必须注意的三种关系 山雨欲来风满楼, 却待山花烂漫时 - 写在.NET 4.0和Visual Studio 2010发布前夜 - Jeffrey Sun [转载]微软4月13日发布Silverlight 4 团队基础生成自动化流程之最佳实践(VI) - 系统模块化条件编译 团队基础生成自动化流程之最佳实践(V) - 使用Desktop Build 团队基础生成自动化流程之最佳实践(IV) - 重写团队基础默认生成流程 谁是你的下一行CODE 团队基础生成自动化流程之最佳实践总论(III) – 重写生成号产生机制 团队基础生成自动化流程之最佳实践总论(I) – 自动化生成流程基础 团队基础生成自动化流程之最佳实践总论(II) – 程序集版本信息 Memory Reordering/Memory Model 及其对.NET的影响 从DWG到XAML (III) – .NET中的 XPS Packaging类库及一个DWFx Packaging类库的实现 (源码) 从DWG到XAML (II) - DWFx格式解析及其和XPS的关系 从DWG到XAML (I) - 浅谈DWG历史,现状及方向 - Jeffrey Sun
当心Dictionary带来的一种隐式内存泄漏
Jeffrey Sun · 2010-01-13 · via 博客园 - Jeffrey Sun

      最近在看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.      // ...
  8.  
  9.      public override bool Equals(object obj)
  10.     {
  11.          if (obj == null)
  12.         {
  13.              return false;
  14.         }
  15.  
  16.          Order orderToCompare = obj as Order;
  17.          if (orderToCompare == null)
  18.         {
  19.              return false;
  20.         }
  21.  
  22.          return ID == orderToCompare.ID &&
  23.              PatronID == orderToCompare.PatronID &&
  24.              BoughtTime == orderToCompare.BoughtTime;
  25.     }
  26.  
  27.      public override int GetHashCode()
  28.     {
  29.          return ("ID" + ID.ToString() +
  30.              "PatronID" + PatronID.ToString() +
  31.              "TimeStamp" + BoughtTime.ToString())
  32.              .GetHashCode();
  33.     }
  34. }

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

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.