Dictionary, SortedDictionary, SortedList 比较

Dictionary, SortedDictionary, SortedList 是 .NET Framework 的三个支持泛型和关键字查找的类, 都属于 System.Collections.Generic 命名空间. 它们无论是名字还是功能都十分相似, 以至于实际运用的时候我们会经常混淆. 因此有必要比较一下它们.

1. 实现
查阅 MSDN 得到如下资料:
Dictionary<(Of <(TKey, TValue>)>) 泛型类提供了从一组键到一组值的映射。字典中的每个添加项都由一个值及其相关联的键组成。通过键来检索值的速度是非常快的,接近于 O(1),这是因为 Dictionary<(Of <(TKey, TValue>)>) 类是作为一个哈希表来实现的。
检索速度取决于为 TKey 指定的类型的哈希算法的质量。

可见, Dictionary 基本上就是一个 Hashtable. 不过它比 Hashtable 类快, 因为它支持泛型~ (稍后我们会用实验证明, 即使使用 Object 类型的 Dictionary 也比 Hashtable 稍快).

--- 华丽的分割线 ---

SortedDictionary<(Of <(TKey, TValue>)>) 泛型类是检索运算复杂度为 O(log n) 的二叉搜索树,其中 n 是字典中的元素数。就这一点而言,它与 SortedList<(Of <(TKey, TValue>)>) 泛型类相似。这两个类具有相似的对象模型,并且都具有 O(log n) 的检索运算复杂度。这两个类的区别在于内存的使用以及插入和移除元素的速度:

SortedList<(Of <(TKey, TValue>)>) 使用的内存比 SortedDictionary<(Of <(TKey, TValue>)>) 少。

SortedDictionary<(Of <(TKey, TValue>)>) 可对未排序的数据执行更快的插入和移除操作:它的时间复杂度为 O(log n),而 SortedList<(Of <(TKey, TValue>)>) 为 O(n)。

如果使用排序数据一次性填充列表,则 SortedList<(Of <(TKey, TValue>)>) 比 SortedDictionary<(Of <(TKey, TValue>)>) 快。

每个键/值对都可以作为 KeyValuePair<(Of <(TKey, TValue>)>) 结构进行检索,或作为 DictionaryEntry 通过非泛型 IDictionary 接口进行检索。

只要键用作 SortedDictionary<(Of <(TKey, TValue>)>) 中的键,它们就必须是不可变的。SortedDictionary<(Of <(TKey, TValue>)>) 中的每个键必须是唯一的。键不能为 nullNothingnullptrnull 引用(在 Visual Basic 中为 Nothing),但是如果值类型 TValue 为引用类型,该值则可以为空。

SortedDictionary<(Of <(TKey, TValue>)>) 需要比较器实现来执行键比较。可以使用一个接受 comparer 参数的构造函数来指定 IComparer<(Of <(T>)>) 泛型接口的实现;如果不指定实现,则使用默认的泛型比较器 Comparer<(Of <(T>)>)..::.Default。如果类型 TKey 实现 System..::.IComparable<(Of <(T>)>) 泛型接口,则默认比较器使用该实现。

C# 语言的 foreach 语句(在 C++ 中为 for each,在 Visual Basic 中为 For Each)需要集合中每个元素的类型。由于 SortedDictionary<(Of <(TKey, TValue>)>) 的每个元素都是一个键/值对,因此元素类型既不是键的类型,也不是值的类型。而是 KeyValuePair<(Of <(TKey, TValue>)>) 类型。

可见, SortedDictionary 类似一个平衡二叉查找树 (AVL). 既然是 BST, 我们当然可以对其进行中序遍历. 有两种方法:
1. For Each
2. Object.GetEnumerator

小实验:

  1. Dim TestObject As New SortedDictionary(Integer, Integer)
  2. With TestObject
  3.     .Add(7,2)
  4.     .Add(0,1)
  5.     .Add(5,3)
  6.     .Add(1,1)
  7.     .Add(4,4)
  8. End With
  9. For Each kvp As Collections.Generic.KeyValuePair(Of Integer, Integer) In TestObject
  10.     MsgBox kvp.Key
  11. Next
复制代码

得到的顺序是 0, 1, 4, 5, 7 (SortedList 同样)
但是如果把 SortedDictionary 换成 Dictionary, 结果就是 7, 0, 5, 1, 4.

另一种遍历方法:

  1. With TestObjectx.GetEnumerator()
  2.     While .MoveNext()
  3.         MsgBox(.Current.Key)
  4.     End While
  5. End With
复制代码

--- 华丽的分割线 ---

SortedList<(Of <(TKey, TValue>)>) 泛型类是具有 O(log n) 检索的二进制搜索树,其中 n 是字典中元素的数目。就这一点而言,它与 SortedDictionary<(Of <(TKey, TValue>)>) 泛型类相似。这两个类具有相似的对象模型,并且都具有 O(log n) 的检索运算复杂度。这两个类的区别在于内存的使用以及插入和移除元素的速度:

SortedList<(Of <(TKey, TValue>)>) 使用的内存比 SortedDictionary<(Of <(TKey, TValue>)>) 少。

SortedDictionary<(Of <(TKey, TValue>)>) 可对未排序的数据执行更快的插入和移除操作,它的运算复杂度为 O(log n),而 SortedList<(Of <(TKey, TValue>)>) 的运算复杂度为 O(n)。

如果使用排序数据一次性填充列表,则 SortedList<(Of <(TKey, TValue>)>) 比 SortedDictionary<(Of <(TKey, TValue>)>) 快。

SortedDictionary<(Of <(TKey, TValue>)>) 类和 SortedList<(Of <(TKey, TValue>)>) 类之间的另一个区别是:SortedList<(Of <(TKey, TValue>)>) 支持通过由 Keys 和 Values 属性返回的集合对键和值执行高效的索引检索。访问此属性时无需重新生成列表,因为列表只是键和值的内部数组的包装。

二叉树的插入操作怎么是 O(n)?

网上有一种说法, 就是 SortedList 内部就是两个数组, 插入的时候类似 O(n^2) 的插入排序 (每个动作为 O(n)), 不过插入有序数据特别快 (每个动作变成 O(1)). 同样的情况出现在删除数据.

  1. Dim TestObject As New SortedList(Of Integer, Integer)
  2. For i As Integer = 1 To 1000000
  3.     TestObject.Add(i, RandomGenerator.Next())
  4. Next
复制代码

当然, RandomGenerator 是我们的随机数发生器:

  1. Dim RandomGenerator As New Random
复制代码

上述代码执行速度相当快, 因为插入的数据的 Key 值是有序的.
如果把 i 换成 1000000 - i, 则速度立刻慢得惨不忍睹.
同样的情况出现在把 i 替换成随机数. 在一段时间的等待后出错: 因为 Key 值不能重复.
这样说来, SortedList 不太像二叉树结构.

SortedList 还有一个功能, 就是直接访问 Key 值大小排名为 k 的 Key 和 Value.
方法是 Object.Keys(k) 和 Object.Values(k).
这更加印证了网上的说法.

我认为 SortedList 没什么用 - 除非是对基本有序的数据, 或者对内存非常吝啬. 如果仅仅需要在 BST 上加上查找排名为 k 的节点的功能, 可以使用一个经典算法: 在每个节点上加上一个 leftsize, 储存它左子树的大小. (当然也可以用 CQF 的 SBT. 那个 SB maintain... 扯远了~)

2. 功能
这三个类的功能上面都讲得差不多了. 因为实现就决定了功能. 这里小结一下.
Dictionary 的功能:
Add<K,V>, Clear, Contains<K/V>, GetCount, Enumerator (无序), GetItem<K>, Remove<K>
SortedDictionary 新增的功能:
Enumerator 为有序 - 对应 BST 的中序遍历.
SortedList 新增的功能:
Capacity(Set/Get) - 毕竟人家是数组
IndexOfKey, IndexOfValue (返回 Value 对应 Key 的排名而不是 Value 的排名)
Keys(k), Values(k) - 返回按照 Key 排序的数组的第 k 个元素

3. 速度
实践出真知 - 某名人.
理论和实践不符就是错的 - Thity.

我们的测试程序:

  1. Module DictionarySpeedTest
  2.     Dim RandomGenerator As New Random
  3.     Dim ArrayListData As New List(Of Key_N_Data)
  4.     Dim TestObject As New Dictionary(Of Long, Long)
  5.     Structure Key_N_Data
  6.         Dim Key As Int64
  7.         Dim Data As Int64
  8.     End Structure
  9.     Const ITEM_COUNT As Integer = 1000000
  10.     Const TEST_COUNT As Integer = 500000
  11.     Dim LastTick As Long
  12.     Sub TimerStart(ByVal Text As String)
  13.         Console.Write(Text)
  14.         LastTick = Now.Ticks
  15.     End Sub
  16.     Sub TimerEnd()
  17.         Dim t As Integer = Now.Ticks - LastTick
  18.         Console.WriteLine(((t) \ 10000).ToString() & " ms")
  19.     End Sub
  20.     Sub Main()
  21.         Process.GetCurrentProcess.PriorityClass = ProcessPriorityClass.High
  22.         Console.WriteLine(TestObject.GetType().ToString())
  23.         TimerStart("Generating data... ")
  24.         For i As Integer = 1 To ITEM_COUNT
  25.             Dim ThisKeyData As Key_N_Data
  26.             ThisKeyData.Key = (CLng(RandomGenerator.Next()) << 31) or RandomGenerator.Next()
  27.             ThisKeyData.Data = (CLng(RandomGenerator.Next()) << 31) or RandomGenerator.Next()
  28.             ArrayListData.Add(ThisKeyData)
  29.         Next
  30.         TimerEnd()
  31.         TimerStart("Test 1: add data test... ")
  32.         For Each Item As Key_N_Data In ArrayListData
  33.             TestObject.Add(Item.Key, Item.Data)
  34.         Next
  35.         TimerEnd()
  36.         TimerStart("Test 2: find data test... ")
  37.         For i As Integer = 1 To TEST_COUNT
  38.             With ArrayListData.Item(RandomGenerator.Next(0, ITEM_COUNT))
  39.                 If Not Equals(TestObject(.Key), .Data) Then MsgBox("Error!")
  40.             End With
  41.         Next
  42.         TimerEnd()
  43.         TimerStart("Test 3: remove data test...")
  44.         For i As Integer = 1 To TEST_COUNT
  45.             TestObject.Remove(ArrayListData.Item(RandomGenerator.Next(0, ITEM_COUNT)).Key)
  46.         Next
  47.         TimerEnd()
  48.     End Sub
  49. End Module
复制代码

通过更改 TestObject 的类型, 我们可以很方便地比较这三个类的速度. 测试结果:

                 ADD    FIND   REMOVE
Dictionary       265ms  203ms  187ms
SortedDictionary 1843ms 828ms  1234ms
SortedList       N/A

我们把 ITEM_COUNT 和 TEST_COUNT 都减小 10 倍:

                 ADD    FIND   REMOVE
Dictionary       15ms   31ms   15ms
SortedDictionary 93ms   46ms   38ms
SortedList       8031ms 15ms   6046ms

SortedList 的随机查找居然比 Dictionary 和 SortedDictionary (Hashtable 和 BST) 还要快. 这样说来, SortedList 似乎又不是简单的数组了. (不过我仍然觉得它没什么用)

4. 小结
如果只是当作索引使用, 请用 Dictionary.
如果需要查找最小的几个元素, 或者需要按顺序遍历元素, 就用 SortedDictionary.
如果输入/删除的元素是基本增序的, 或者访问次数远多于修改次数, 或者需要访问第 k 大的元素, 或者对内存吝啬得 BT 的情况, 用 SortedList 吧. (它居然成使用情况最多的了... orz)

PS: 微软似乎也很吝啬, SortedDictionary 居然只支持增序 (默认的比较器), 如果要降序的话, 我们得自己写一个比较器.

  1. Class MyComparer
  2.     Inherits Comparer(Of Long)
  3.     Public Overrides Function Compare(ByVal x As Long, ByVal y As Long) As Integer
  4.         Return Comparer(Of Long).Default.Compare(y, x)
  5.     End Function
  6. End Class
  7. Dim TestObject As New SortedList(Of Long, Long)(New MyComparer)
复制代码

现在我们可以来进行一下刚开始的时候提到的 Dictionary vs Hashtable 对决.

  1. Const ITEM_COUNT As Integer = 1000000
  2. Const TEST_COUNT As Integer = 500000
复制代码

ADD   FIND  REMOVE
Dictionary(Of Long, Long)     271ms 203ms 187ms
Dictionary(Of Object, Object) 468ms 312ms 234ms
Hashtable                     859ms 390ms 218ms

结论: 最好用 Dictionary 代替 Hashtable.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
trade_create_by_buyer-CSHARP-UTF-8 │ ├app_code ┈┈┈┈┈┈┈┈┈┈类文件夹 │ │ │ ├AlipayConfig.cs┈┈┈┈┈基础配置类文件 │ │ │ ├AlipayCore.cs┈┈┈┈┈┈支付宝接口公用函数类文件 │ │ │ ├AlipayNotify.cs┈┈┈┈┈支付宝通知处理类文件 │ │ │ ├AlipaySubmit.cs┈┈┈┈┈支付宝各接口请求提交类文件 │ │ │ └MD5.cs ┈┈┈┈┈┈┈┈┈MD5类库 │ ├log┈┈┈┈┈┈┈┈┈┈┈┈┈日志文件夹 │ ├default.aspx ┈┈┈┈┈┈┈┈支付宝接口入口文件 ├default.aspx.cs┈┈┈┈┈┈┈支付宝接口入口文件 │ ├notify_url.aspx┈┈┈┈┈┈┈服务器异步通知页面文件 ├notify_url.aspx.cs ┈┈┈┈┈服务器异步通知页面文件 │ ├return_url.aspx┈┈┈┈┈┈┈页面跳转同步通知文件 ├return_url.aspx.cs ┈┈┈┈┈页面跳转同步通知文件 │ ├Web.Config ┈┈┈┈┈┈┈┈┈配置文件(集成时删除) │ └readme.txt ┈┈┈┈┈┈┈┈┈使用说明文本 ※注意※ 需要配置的文件是: alipay_config.cs default.aspx default.aspx.csreturn_url.aspx return_url.aspx.cs notify_url.aspx notify_url.aspx.cs统一命名空间为:namespace Com.Alipiay ───────── 类文件函数结构 ───────── AlipayCore.cs public static Dictionary<string, string> ParaFilter(SortedDictionary<string, string> dicArrayPre) 功能:除去数组中的空值和签名参数并以字母a到z的顺序排序 输入:SortedDictionary<string, string> dicArrayPre 过滤前的参数组 输出:Dictionary<string, string> 去掉空值与签名参数后的新签名参数组 public static string CreateLinkString(Dictionary<string, string> dicArray) 功能:把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 输入:Dictionary<string, string> dicArray 需要拼接的数组 输出:string 拼接完成以后的字符串 public static string CreateLinkStringUrlencode(Dictionary<string, string> dicArray, Encoding code) 功能:把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串,并对参数值做urlencode 输入:Dictionary<string, string> dicArray 需要拼接的数组 Encoding code 字符编码 输出:string 拼接完成以后的字符串 public static void log_result(string sPath, string sWord) 功能:写日志,方便测试(看网站需求,也可以改成存入数据库) 输入:string sPath 日志的本地绝对路径 string sWord 要写入日志里的文本内容 public static string GetAbstractToMD5(Stream sFile) 功能:获取文件的md5摘要 输入:Stream sFile 文件流 输出:string MD5摘要结果 public static string GetAbstractToMD5(byte[] dataFile) 功能:获取文件的md5摘要 输入:byte[] dataFile 文件流 输出:string MD5摘要结果 ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ MD5.cs public static string Sign(string prestr, string key, string _input_charset) 功能:签名字符串 输入:string prestr 需要签名的字符串 string key 密钥 string _input_charset 编码格式 输出:string 签名结果 public static bool Verify(string prestr, string sign, string key, string _input_charset) 功能:验证签名 输入:string prestr 需要签名的字符串 string sign 签名结果 string key 密钥 string _input_charset 编码格式 输出:string 验证结果 ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ AlipayNotify.cs public Notify() 功能:构造函数 从配置文件中初始化变量 public bool Verify(SortedDictionary<string, string> inputPara, string notify_id, string sign) 功能:验证消息是否是支付宝发出的合法消息 输入:SortedDictionary<string, string> inputPara 通知返回参数数组 string notify_id 通知验证ID string sign 支付宝生成的签名结果 输出:bool 验证结果 private string GetPreSignStr(SortedDictionary<string, string> inputPara) 功能:获取待签名字符串(调试用) 输入:SortedDictionary<string, string> inputPara 通知返回参数数组 输出:string 待签名字符串 private bool GetSignVeryfy(SortedDictionary<string, string> inputPara, string sign) 功能:获取返回回来的待签名数组签名后结果 输入:SortedDictionary<string, string> inputPara 通知返回参数数组 string sign 支付宝生成的签名结果 输出:bool 签名验证结果 private string GetResponseTxt(string notify_id) 功能:获取是否是支付宝服务器发来的请求的验证结果 输入:string notify_id 通知验证ID 输出:string 验证结果 private string Get_Http(string strUrl, int timeout) 功能:获取远程服务器ATN结果 输入:string strUrl 指定URL路径地址 int timeout 超时时间设置 输出:string 服务器ATN结果字符串 ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ AlipaySubmit.cs private static string BuildRequestMysign(Dictionary<string, string> sPara) 功能:生成签名结果 输入:Dictionary<string, string> sPara 要签名的数组 输出:string 签名结果字符串 private static Dictionary<string, string> BuildRequestPara(SortedDictionary<string, string> sParaTemp) 功能:生成要请求给支付宝的参数数组 输入:SortedDictionary<string, string> sParaTemp 请求前的参数数组 输出:Dictionary<string, string> 要请求的参数数组 private static string BuildRequestParaToString(SortedDictionary<string, string> sParaTemp, Encoding code) 功能:生成要请求给支付宝的参数数组 输入:SortedDictionary<string, string> sParaTemp 请求前的参数数组 Encoding code 字符编码 输出:string 要请求的参数数组字符串 public static string BuildRequest(SortedDictionary<string, string> sParaTemp, string strMethod, string strButtonValue) 功能:建立请求,以表单HTML形式构造(默认) 输入:SortedDictionary<string, string> sParaTemp 请求参数数组 string strMethod 提交方式。两个值可选:post、get string strButtonValue 确认按钮显示文字 输出:string 提交表单HTML文本 public static string BuildRequest(SortedDictionary<string, string> sParaTemp) 功能:建立请求,以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果 输入:SortedDictionary<string, string> sParaTemp 请求参数数组 输出:string 支付宝处理结果 public static string BuildRequest(SortedDictionary<string, string> sParaTemp, string strMethod, string fileName, byte[] data, string contentType, int lengthFile) 功能:建立请求,以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果 输入:SortedDictionary<string, string> sParaTemp 请求参数数组 string strMethod 提交方式。两个值可选:post、get string fileName 文件绝对路径 byte[] data 文件数据 string contentType 文件内容类型 int lengthFile 文件长度 输出:string 支付宝处理结果 public static string Query_timestamp() 功能:用于防钓鱼,调用接口query_timestamp来获取时间戳的处理函数 输出:string 时间戳字符串 ────────── 出现问题,求助方法 ────────── 如果在集成支付宝接口时,有疑问或出现问题,可使用下面的链接,提交申请。 https://b.alipay.com/support/helperApply.htm?action=supportHome 我们会有专门的技术支持人员为您处理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值