多映射通用集合类(C#实现)--支持一键多值存储

.net的通用Dictionary集合类有一个“键”唯一约束。考虑这样一种情况:你想在Dictionary中存Author
Name以及Articles。首先,你想加入Bob->Article_Good_One,而当你想加入Bob->Article_Good_Second,你将得到一个异常。这是因为Dictionary的唯一键约束。Dictionary拒绝接受相同的key,因为它要求键唯一。

Dictionary类被设计成对搜索具有很高的性能。而多映射类在你想让搜索具有很高的性能以及让它可以为一个相同的键增加多个值的时候可以使用。

背景

Dictionary通用集合是一个很好的数据结构。很多的一种情况,在程序开发或者开发服务的时候,我们需要为一个通常的Key存储多个value。一个简单的例子就是一个人有多个电话号码,而访问级别需要基于人。

使用Dictionary时,代码大致如下:

现在让我们来看一下,使用多映射类集合类,怎样来实现这个简单的例子:

第一行创建了一个MultiMapBK对象。接下来的四行加入了四个人以及他们的电话号码。然后,我们需要为这四个人设置其他的值。接下来的一些行用来检索“Mai”以及打印所有的值。

多映射集合类的说明

下面的图片解释了MultiMapBK的内部数据结构。

就像上面的图片显示的,每一个值都被存储在一个List对象中。而List能够为一个key存储超过一个value。

而这个多映射集合类也是类型安全和线程安全的。它也支持当一个线程在修改集合时,另一个线程可以枚举它。

什么是一个多映射集合

它最基本的功能是为一个键存储多个值,然而同时,它也是一个“并发”集合。下面的图片给出了一个多映射能够实现的功能。

使用代码

使用这个集合类需要下面的几个步骤:

1) 在VS2005或者2008的解决方案中,右击“引用”

2) 点击“添加引用”同时加入下载下来的MultiMap.dll【附于文章结束的链接中,自行下载】

现在,让我们来看一下,如何使用这段代码

1、 怎样定义以及为该集合加入元素:

上面的第一行创建了一个新的集合对象。2,3,4行加入了几个元素。注意,键“Alice”在所有的三个记录中是相同的。

2、 怎样从集合中读取元素

1,2,3,4行创建以及实例化了一个集合,第五行获得了一个枚举器来枚举集合中的每一个元素。第七行,使用了MoveNext()来读取一个特殊键(Alice)的每一个值。

3、 怎样来快速定位一个特别的项并打印出其所有的值

多映射集合的设计

内部使用的数据结构

基本的内部结构如下图:

如上面所说的,Dictionary<Key, List<Value>>是用来为一个相同的键存储多个值得数据结构。

线程访问场景一

和通常的设计一样,为了线程安全,集合类在增加,修改,以及删除的时候是被锁住的。它产生了一个很小的(通常是毫秒级别的)开销。但是为这个类的使用者提供了巨大的灵活性。这样这个集合类的使用者就不需要再担心线程安全,同步等问题。

考虑这样一种情况:线程1正在读取集合中第五键的值。现在,线程2将该值删除。那么线程一的下一次读取应该返回null。锁住内部的数据结构可以完成这个功能。

线程访问场景二

下一个场景是线程感知。考虑这样一种情况,两个不同的线程都正在枚举同一个键,该键有100个值,正如下面的图片显示的:

现在,当线程2被实例化来读取Key_5的值的时候,线程1已经读取了该键的5个值。而现在,下一次对MultiMap's
GetCurrentItem方法的调用,需要知道返回哪一个值。例如,是对线程1来讲的第六个值还是对线程2来讲的第一个值。为了应对这样的情况,MultiMap存储了线程的明细在一个独立的集合中。它检索一个值来查看哪个值是被哪个线程最后一次读取的。使用线程的明细,MultiMap将能够为正确的线程存储正确的值。而用多线程执行下面的代码,也仍然是正确的:

线程访问场景三

下一步是完成在枚举时的线程安全。

考虑如下的场景,线程一使用MoveNext()来获取Current属性。与此同时,线程2删除了被线程1“命中”的当前项。如果这个场景不被处理,我们将会在线程1上看到一个有趣的“NullReferenceException”异常。一个基本的想法就是:当线程1调用MoveNext()方法的时候,让它拥有该Current属性。然而,当线程2企图删除线程1的Current时,有两个选择提供给线程2。选择1:线程2可以阻塞“删除”,直到线程一调用MoveNext方法或者移动到下一项时,删除调用才返回。选择2:线程2可以采用不阻塞地调用删除方法,并且调用可以立即返回。但该项将被标识为“删除项”,但此时仍然存在。当线程1从该被标识的项上移走时,该项将会被安全地从集合中删除。

为了简便起见,让我们离开这些并发场景。然而,主要的并发问题被列举在下一节中。

解决并发问题

讨论所有的并发问题的处理,将会使这篇文章变得非常长。然而,列举出那些需要被处理的并发问题,却是很有用的:

1) 多线程更新(或者插入),在同一个MultiMap集合的实例上。项以它们接受到的顺序添加。

2)
在不同的线程之间枚举。例如,一个线程创建了一个枚举器,但是被传递给另一个线程使用?允许,甚至允许在更多的线程之间共享同一个枚举器的实例,每一个线程将获得它自己正确的“next_item”(在方法MoveNext上)和Current。

3)
一个线程从集合中删除一项,而此时另一个线程正在枚举或者读取相同的项?允许。一旦线程通过读取元素的MoveNext方法,或thread_abort或thread_close离开该元素,那么删除将生效。然而,有趣的是,当删除这个项的线程再次枚举该删除的项(通过Reset方法),那将不能看到被删除的项。这也将应用在任何{除了正在读取被删除元素的线程之外的}所有线程。

4) 多线程同时删除同一个项?如果没有其他的线程正在访问该元素,它将被安全地删除。

5) 一个线程正在删除一个元素,而另一个线程正在增加一个同名的元素?这依赖于哪一个线程首先获得“锁”。

6) 一个线程访问一项,然后终止了。之后的某个时间,另一个线程企图删除这项?该项将会被删除。

7) 一个线程删除了一项,而另一个线程此时正在读该项。同时第三个线程,企图枚举该项?该项对第三个线程将是不可见的。

8) 为什么在API中没有暴露Count属性(元素的个数)?是为了避免糟糕的代码。

为什么线程安全的枚举是重要的

我们有一个UI来打印从Dictionary集合中的值。为了让该集合会有一些比较灵活的(或者比较新的)值,我们用一个线程来更新它。现在,用户按下刷新键。跟随下面的图片中基于红色序列号的事件。结果是有一个InvalidOperationException异常将被Dictionary集合的枚举器抛出。

让我们通过锁住“枚举”以及“更新”操作来解决这个异常。但是考虑这个解决方案,当用户按下刷新按钮时,后台的更新仍然在继续。而UI将被挂起。这个行为是不可取的。

现在,考虑相同的场景下使用MultiMap集合。UI线程刷新以及更新线程可以被同步执行。下面的图片解释了,这个场景:

下面的代码是一个用Dictionary类做的一个简单的例子:

MultiMap通过提供一种内建的线程安全机制,下面的更简短的代码以及解决了这个问题:

输出:

有什么不同

使用一个锁住的Dictionary集合与使用MultiMap集合有什么区别?

1) 多线程枚举

这意味着什么?通常,枚举器不能够在一个线程上使用,当另一个线程正在更新相同的集合时。MultiMap集合则支持这样的操作。不同的线程可以更新、修改以及枚举MultiMap集合。

2) 线程感知枚举器

这又意味着什么?考虑到Dictionary集合在一个WCF服务中使用。通常,多线程可能服务多个会话。这种情况下,Dictionary集合需要被一个线程感知的类包裹来处理“竞争条件”。

3) 资源释放(支持Idisposable接口)

这意味着什么?万一你存储了一个不受托管的资源在MultiMap集合中,它能够支持释放存储的这些资源通过一个调用。难道你不愿意写multiMapObject.Dispose()?而是愿意再枚举每一项的使用调用Dispose吗?

补充:本文给我们提供了一个很到的思路——多线程之间的并发问题,以及.net内置类型的组合问题。你甚至可以做这样的组合Dictionary<Key,
Dictionary<Key,Value>>,来满足你特定场景的存储逻辑。当然,如果支持多线程,那么并发问题,还是得考虑地比较周全。

引用:

http://blogs.msdn.com/pfxteam/archive/2008/08/12/8852005.aspx?CommentPosted=true#commentmessage

http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx

原文链接:

http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx

http://www.codeproject.com/KB/collections/MultiMap_P_2.aspx

示例源码和DLL下载地址:

http://download.csdn.net/detail/yanghua_kobe/3635173

原文发布时间为:2011-09-25

时间: 2024-11-28 21:16:31

多映射通用集合类(C#实现)--支持一键多值存储的相关文章

暴风影音5支持一键续播

暴风影音5 5.35.0327.1111新版本播放列表支持一键续播,用户可轻松找到上次观影位置,此外还优化了大片风暴皮肤,推荐下载更新.暴风影音5.35.0327.1111更新日志: 1.播放列表支持一键续播,轻松找到上次观影位置 2.大片风暴皮肤优化 下载链接: http://dl.baofeng.com/baofeng5/Baofeng5-5.35.0327.exe 查看本栏目更多精彩内容:http://www.bianceng.cnhttp://www.bianceng.cn/soft/M

SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程(支持单表或多表结查集分页)

原文:SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程(支持单表或多表结查集分页) SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程,支持单表或多表结查集分页,存储过程如下: /******************/ --Author:梦在旅途(www.Zuowenjun.cn) --CreateDate:2015-06-02 --Function:分页获取数据 /******************/ crea

MySQL 处理插入过程中的主键唯一键重复值的解决方法_Mysql

本篇文章主要介绍在插入数据到表中遇到键重复避免插入重复值的处理方法,主要涉及到IGNORE,ON DUPLICATE KEY UPDATE,REPLACE:接下来就分别看看这三种方式的处理办法. IGNORE 使用ignore当插入的值遇到主键(PRIMARY KEY)或者唯一键(UNIQUE KEY)重复时自动忽略重复的记录行,不影响后面的记录行的插入, 创建测试表 CREATE TABLE Tignore (ID INT NOT NULL PRIMARY KEY , NAME1 INT )d

【转】kubernetes 中 deployment 支持哪些键值

这个比较全,可以参考 ================= https://www.addops.cn/post/kubernetes-deployment-fileds.html =================   最近在看 kubernetes deployment 部分,按照其文档中的例子进行了一些增删改查DP(deployment 简称DP)的操作,感觉还是很有意思的官方文档.不过,其参考例子都比较简单,要是在生产环境中使用时肯定是不够的,那么问题来了: DP到底支持哪些键值呢? 我花

用字典给Model赋值并支持map键值替换

用字典给Model赋值并支持map键值替换 这个是昨天教程的升级版本,支持键值的map替换. 源码如下: NSObject+Properties.h 与 NSObject+Properties.m // // NSObject+Properties.h // // Created by YouXianMing on 14-9-4. // Copyright (c) 2014年 YouXianMing. All rights reserved. // #import <Foundation/Fou

MSSQL分页存储过程完整示例(支持多表分页存储)_MsSql

本文实例讲述了MSSQL分页存储过程.分享给大家供大家参考,具体如下: USE [DB_Common] GO /****** 对象: StoredProcedure [dbo].[Com_Pagination] 脚本日期: 03/09/2012 23:46:20 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO /************************************************************

MSSQL分页存储过程完整示例(支持多表分页存储)

本文实例讲述了MSSQL分页存储过程.分享给大家供大家参考,具体如下: USE [DB_Common] GO /****** 对象: StoredProcedure [dbo].[Com_Pagination] 脚本日期: 03/09/2012 23:46:20 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO /************************************************************

刷机精灵TV版支持一键刷机乐视ROM 小米盒子

刷机精灵于4月25日正式发布TV版,首发支持小米盒子一键刷入乐视ROM,开启全球首款安卓大屏刷机新纪元.该ROM是刷机精灵联合欧进制团队制作,并由刷机精灵联合ZNDS智能电视网共同发布.此外,陆续还将支持更多品牌的电视盒子一键刷机,以更多优质ROM,让手机刷机发烧延续到电视盒子. 手机刷机给我们带来了更多新颖.流畅.设计感十足,功能众多的ROM,给手机注入了新灵魂.近年来电视盒子.智能电视持续发热,预示着智能大屏时代的到来,用户不再满足单调的系统,而是转向智能化.交互性更强的操作系统.刷机精灵推

【懒人专属】通用RecylerAdapter,内置XRecyclerView,兼容上下拉与动画,高复用,通用所有页面,支持空页

Hello大家好,郭老司机又来了.以前都是看文章的小喵同志,如今终于体也会到码字的不易,作为一个沉默寡言的程序猿,对于码文无数的前辈深表敬佩((/- -)/. RecylerView相信大家都听过(你确定要说没听过 = =),在ListView横行的年代里,RecyclerView携带了褒贬不一的评价,开始进入了我们的视线,那时候刚好开始了新的项目,正好就拿它练手了.下方进入介绍使用流程,建议对着Demo撸起来.( ・᷄-・᷅ ) 正常情况下,对于每一个不同的列表,我们经常需要实现不同的Adap