泛型字典类比较

[转] Dictionary<TKey,TValue>, SortedDictionary<TKey,TValue>, SortedList<TKey,TValue>横向评测

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

1. 实现

查阅 MSDN 得到如下资料:

Dictionary<TKey, TValue>泛型类提供了从一组键到一组值的映射。字典中的每个添加项都由一个值及其相关联的键组成。通过键来检索值的速度是非常快的,接近于O(1),这是因为Dictionary<TKey, TValue>类是作为一个哈希表来实现的。

检索速度取决于为TKey指定的类型的哈希算法的质量。

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

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

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

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

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

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

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

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

C#语言的foreach语句需要集合中每个元素的类型。由于SortedDictionary<TKey, TValue>的每个元素都是一个键/值对,因此元素类型既不是键的类型,也不是值的类型。而是KeyValuePair<TKey, TValue>类型。

可见,SortedDictionary<TKey, TValue>类似一个平衡二叉查找树(AVL)。既然是 BST,我们当然可以对其进行中序遍历。有两种方法:

1. foreach

2. Object.GetEnumerator

小实验:

CODE:

SortedDictionary<int, int> TestObject = new SortedDictionary<int, int>();

TestObject.Add(7, 2);

TestObject.Add(0, 1);

TestObject.Add(5, 3);

TestObject.Add(1, 1);

TestObject.Add(4, 4);

foreach (KeyValuePair<int, int> kvp in TestObject)

{

Console.WriteLine(kvp.Key);

}

得到的顺序是0,1,4,5,7(SortedList<TKey, TValue>同样)

但是如果把SortedDictionary<TKey, TValue>换成Dictionary<TKey, TValue>, 结果就是7,0,5,1,4。

另一种遍历方法:

CODE:

SortedDictionary<int, int>.Enumerator sde = TestObject.GetEnumerator();

while (sde.MoveNext())

{

Console.WriteLine(sde.Current.Key);

}

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

QUOTE:

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

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

CODE:

Random ra = new Random();

SortedList<int, int> TestObject = new SortedList<int, int>();

for (int i = 1; i <= 1000000; i++)

{

TestObject.Add(i, ra.Next());

}

其中,ra.Next()用来生成随机数。

上述代码执行速度相当快,因为插入的数据的Key值是有序的。

如果把i换成1000000-i,则速度立刻慢得惨不忍睹。

同样的情况出现在把i替换成随机数。在一段时间的等待后出错,因为Key值不能重复。

这样说来,SortedList<TKey, TValue>不太像二叉树结构.

SortedList<TKey, TValue>还有一个功能,就是直接访问Key值大小排名为k的Key 和Value。

方法(使用属性)是object.Key[k]和object.Value[k)。

这更加印证了网上的说法.

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

2. 功能

这三个类的功能上面都讲得差不多了。因为实现就决定了功能。这里小结一下。

Dictionary<TKey, TValue>的功能:

Add,Clear,ContainsKey,ContainsValue,Count(属性),Enumerator(无序),Item(属性), Remove

SortedDictionary<TKey, TValue>新增的功能:

Enumerator为有序 - 对应BST的中序遍历。

SortedList<TKey, TValue>新增的功能:

Capacity(属性) - 毕竟人家是数组

IndexOfKey,IndexOfValue(返回Value对应Key的排名而不是 Value 的排名)

Keys[k],Values[k] - 返回按照Key排序的数组的第k个元素

3. 速度

实践出真知 - 某名人。

理论和实践不符就是错的 - Thity。

我们的测试程序:

CODE:

static class DictionarySpeedTest

{

static Random RandomGenerator = new Random();

static List<Key_N_Data> ArrayListData = new List<Key_N_Data>();

static Dictionary<long, long> TestObject = new Dictionary<long, long>();

public struct Key_N_Data

{

public long Key;

public long Data;

}

const int ITEM_COUNT = 1000000;

const int TEST_COUNT = 500000;

static long LastTick;

public static void TimerStart(string Text)

{

Console.Write(Text);

LastTick = DateTime.Now.Ticks;

}

public static void TimerEnd()

{

long t = DateTime.Now.Ticks - LastTick;

Console.WriteLine(((t) / 10000).ToString() + " ms");

}

public static void Main()

{

Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

Console.WriteLine(TestObject.GetType().ToString());

TimerStart("Generating data... ");

for (int i = 1; i <= ITEM_COUNT; i++)

{

Key_N_Data ThisKeyData = default(Key_N_Data);

ThisKeyData.Key = ((long)RandomGenerator.Next() << 31) | RandomGenerator.Next();

ThisKeyData.Data = ((long)RandomGenerator.Next() << 31) | RandomGenerator.Next();

ArrayListData.Add(ThisKeyData);

}

TimerEnd();

TimerStart("Test 1: add data test... ");

foreach (Key_N_Data Item in ArrayListData)

{

TestObject.Add(Item.Key, Item.Data);

}

TimerEnd();

TimerStart("Test 2: find data test... ");

for (int i = 1; i <= TEST_COUNT; i++)

{

{

if (TestObject[ArrayListData[RandomGenerator.Next(0, ITEM_COUNT)].Key] != ArrayListData[RandomGenerator.Next(0, ITEM_COUNT)].Data)

Console.WriteLine("Error!");

}

}

TimerEnd();

TimerStart("Test 3: remove data test...");

for (int i = 1; i <= TEST_COUNT; i++)

{

TestObject.Remove(ArrayListData[RandomGenerator.Next(0, ITEM_COUNT)].Key);

}

TimerEnd();

Console.Read();

}

}

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

ADD    FIND   REMOVE

Dictionary<TKey, TValue>       265ms  203ms  187ms

SortedDictionary<TKey, TValue> 1843ms 828ms  1234ms

SortedList<TKey, TValue>       N/A

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

ADD    FIND   REMOVE

Dictionary<TKey,TValue>       15ms   31ms   15ms

SortedDictionary<TKey,TValue> 93ms   46ms   38ms

SortedList<TKey,TValue>       8031ms 15ms   6046ms

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

4. 小结

如果只是当作索引使用,请用Dictionary<TKey,TValue>。

如果需要查找最小的几个元素,或者需要按顺序遍历元素,就用SortedDictionary<TKey,TValue>。

如果输入/删除的元素是基本增序的,或者访问次数远多于修改次数,或者需要访问第k大的元素,或者对内存吝啬得BT的情况,用SortedList<TKey,TValue>吧。(它居然成使用情况最多的了... orz)

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

CODE:

class MyComparer : Comparer<long>

{

public override int Compare(long x, long y)

{

return Comparer<long>.Default.Compare(y, x);

}

}

使用:

SortedList<long, long> TestObject = new SortedList<long, long>(new MyComparer());

现在我们可以来进行一下刚开始的时候提到的Dictionary<TKey,TValue>(泛型)vs Hashtable(非泛型)对决。

结果:

ADD   FIND  REMOVE

Dictionary(Of Long, Long)     271ms 203ms 187ms

Dictionary(Of Object, Object) 468ms 312ms 234ms

Hashtable                         859ms 390ms 218ms

结论: 最好用Dictionary代替Hashtable。

时间: 2024-10-20 15:40:57

泛型字典类比较的相关文章

python中将普通对象作为 字典类(dict) 使用

目前我知道的有两种方法: 1 定义的类继承dict类 例如 class A(dict): pass a = A() a['name'] = 12 2 给自定义的类添加 __setitem__() __getitem__()方法 class A: def __init__(self, cfg={}): <strong><span style="color:#ff0000;">self.cfg = cfg</span></strong> de

Swift字典类

在Foundation框架中提供一种字典集合,它是由"键-值"对构成的集合.键集合不能重复,值集合没有特殊要求.键和值集合中的元素可以是任何对象,但是不能是nil.Foundation框架字典类也分为NSDictionary不可变字典和NSMutableDictionary可变字典.一.NSDictionary类 NSDictionary有很多方法和属性,下面总结其常用的方法和属性. initWithDictionary: 构造器,通过Swift的Dictionary创建NSDicti

爪哇国新游记之十八----泛型栈类

import java.lang.reflect.Array; /** * 泛型栈 * * @param <T> */ public class Stack<T>{ private Class<T> type;// 栈元素所属的类 private int size;// 栈深度 private T[] arr;// 用数组存储 private int top;// 栈顶元素的下标 public Stack(Class<T> type,int size){ t

开发积累—泛型工具类

前言:使用SSH2中使用的泛型工具类,以前写泛型比较麻烦.今天收集到一个工具类,好东呀!!分享给大家,绝对有用.JAVA版的web应用程序使用. 示例代码: import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** *Hibernate工具类,用于获取S

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程 --一个泛型句柄类 引言: [小心地雷] 这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板.在熟悉了这些特性之后再研究这个例子也许会帮助.另一方面,这个例子还能很好地测试你对这些特性的理解程度. 前面示例的Sales_item和Query两个类的使用计数的实现是相同的.这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数.原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化.至于是公开还是隐藏下

字典类(十一)

字典类 键值对:key-value 键值对,用一个比喻来说,就是有一堆杂乱的,无序的货物,要怎么样去找到某个货物,并把货物拿出来.那么这里就要用标签去给货物贴上,这样就能在这堆货物里,通过标签,找到自己想要的货物了. 键值对就是类似于这样的存在,我们用一个键key来保存名字,用value来保存实际的值,这样就可以直接通过这个key来取到这个值value. key相当于货物的标签,value相当于实际的货物. 注意:一堆数据里,key不能重复. 不可变字典NSDictionary: 字典是用来保存

[转载]python中将普通对象作为 字典类(dict) 使用

目前我知道的有两种方法: 1 定义的类继承dict类 例如 class A(dict): pass a = A() a['name'] = 12 2 给自定义的类添加 __setitem__() __getitem__()方法 class A: def __init__(self, cfg={}): self.cfg = cfg def __setitem__(self, key, value): self.cfg[key] = value def __getitem__(self, key):

Objective-C中字典类

字典是用于保存具有映射关系(key-value对)的数据集合.一个key-value对认为是一个条目(Entry),字典是存储key-value对的容器. 字典类的特点 与数组不同,字典靠key存取元素; key值不能重复,value必须是对象; 键值对在字典中是无序存储的. 字典分为不可变字典(NSDictionary)和可变字典(NSMutableDictionary). 不可变字典(NSDictionary) 字典一旦创建,键值对不可更改,不可添加, 不可删除. 仅能读取key或value

TStringList 与 泛型字典TDictionary 的 哈希功能效率PK

结论:做HashMap 映射 功能的时候 ,字典TDictionary 功能更强大,且效率更高,比如不仅仅可以存String,还可以存结构和类. unit Unit5; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, S