字典表示一种复杂的数据结构,这种数据结构允许按照某个键来访问元素。字典也称为映射或散列表。
  字典的主要特性是能根据键快速查找值。也可以自由添加和删除元素,这有点像List<T>(http://www.cnblogs.com/afei-24/p/6824791.html),但没有在内存中移动后续元素的性能开销。
  下图是一个简化表示,键会转换位一个散列。利用散列创建一个数字,它将索引和值关联起来。然后索引包含一个到值的链接。一个索引项可以关联多个值,索引可以存储为一个树型结构。
  
  .NET Framework提供了几个字典类。最主要的类是Dictionary<TKey,TValue>。

1.键的类型
  用作字典中的键的类型必须重写Object类的GetHashCode()方法。只要字典类需要确定元素的位置,它就要调用GetHashCode()方法。GetHashCode()方法返回的int有字典用于计算在对应位置放置元素的索引。后面介绍这个算法,现在只需要知道它涉及素数,所以字典的容量是一个素数。
  GetHashCode()方法的实现代码必须满足的要求:
    *相同的对象应总是返回相同的值
    *不同的对象可以返回相同的值
    *它应执行的比较快,计算的开销不大
    *它不能抛出异常
    *它应至少使用一个实例字段
    *散列代码值应平均分布在int可以存储的这个数字范围上
    *散列代码最好在对象的生存期中不发生变化
  字典的性能取决于GetHashCode()方法的实现代码。

  散列代码值应平均分布在int可以存储的这个数字范围上的原因:
  如果两个键返回的散列代码值会得到相同的索引,字典类就必须寻找最近的可用空闲位置来存储第二个数据项,这需要进行一定的搜索,以便以后检索这一项。显然这会降低性能,如果在排序的时候许多键都有相同的索引这中冲突会更可能出现。根据Microsoft的算法工作方式,当计算出来的散列代码值平均分布在int.MinValue和int.MaxValue之间时,这种风险会降到最低。

  除了实现GetHashCode()方法之外,键类型还必须实现IEquatable<T>.Equals()方法,或重写Object.Equals()方法。0因为不同的键对象可能返回相同的散列代码,所以字典使用Equals()方法来比较键。字典检查两个键A和B是否相等,并调用A.Equals(B)方法。这表示必须确保下述条件总是成立:
    如果A.Equals(B)返回true,则A.GetHashCode()和B.GetHashCode()总是返回相同的散列代码。
  这听起来有点奇怪,但它很重要。如果上述条件不成立,这个字典还能工作,但会出现,把一个对象放在字典中后,就再也检索不到它,或者返回了错误的项。
  所以,如果为Equals()方法提供了重写版本,但没提供GetHashCode()方法的重写版本,C#编译器会显示一个警告。

  对于System.Object,这个条件为true,因为Equals()方法只是比较引用,GetHashCode()方法实际上返回一个仅基于对象地址的散列代码。这说明,如果散列表基于一个键,而该键没有重写这些方法,这个散列表可以工作,但只有对象完全相同,键才被认为是相等的。也就是说,把一个对象放在字典中时,必须将它与该键的引用关联起来。也不能以后用相同的值实例化另一个键对象。如果没有重写Equals()方法和GetHashCode()方法,在字典中使用类型时就不太方便。

  System.String实现了IEquatable接口,并重载了GetHashCode()方法。Equals()方法提供了值的比较,GetHashCode()方法根据字符串的值返回一个散列代码。因此,在字典中把字符串用在键很方便。

  数字类型(如Int32)也实现了IEquatable接口,并重载了GetHashCode()方法。但是这些类型返回的散列代码只能映射到值上。如果希望用作键的数字本身没有分布在可能的整数值范围内,把整数用作键就不能满足键值的平均分布规则,于是不能获得最佳的性能。Int32并不适合在字典中使用。

  如果需要使用的键类型没有实现IEquatable接口,并根据存储在字典中的键值重载GetHashCode()方法,就可以创建一个实现IEqualityComparer<T>接口的比较器。IEqualityComparer<T>接口定义了GetHashCode()方法和Equals()方法,并将传递的对象作为参数,这样可以提供与对象类型不同的实现方式。

2.演示字典
  创建一个员工ID(EmployeeId)结构,用作字典的键。存储在字典中的数据是Employee类型的对象。
  该结构的成员是表示员工的一个前缀字符和一个数字。这两个变量都是只读的,只能在构造函数中初始化。字典中的键不应改变,这是必须保证的。
   

      public struct EmployeeId : IEquatable<EmployeeId>
{
private readonly char prefix;
private readonly int number; public EmployeeId(string id)
{
Contract.Requires<ArgumentNullException>(id != null); prefix = (id.ToUpper())[];
int numLength = id.Length - ;
try
{
number = int.Parse(id.Substring(, numLength > ? : numLength));
}
catch (FormatException)
{
throw new Exception("Invalid EmployeeId format");
}
} public override string ToString()
{
return prefix.ToString() + string.Format("{0,6:000000}", number);
} //由于没有填满整数取值范围,GetHashCode方法将数字向左移动16位,再与原来的数字进行异或操作,
//最后将结果乘以16进制数0x15051505。这样,散列代码在整数取值区域上的分布就很均匀。
public override int GetHashCode()
{
return (number ^ number << ) * 0x15051505;
} public bool Equals(EmployeeId other)
{
if (other == null) return false; return (prefix == other.prefix && number == other.number);
}
//比较两个EmployeeId对象的值
public override bool Equals(object obj)
{
return Equals((EmployeeId)obj);
} public static bool operator ==(EmployeeId left, EmployeeId right)
{
return left.Equals(right);
} public static bool operator !=(EmployeeId left, EmployeeId right)
{
return !(left == right);
}
} public class Employee
{
private string name;
private decimal salary;
private readonly EmployeeId id; public Employee(EmployeeId id, string name, decimal salary)
{
this.id = id;
this.name = name;
this.salary = salary;
} public override string ToString()
{
return String.Format("{0}: {1, -20} {2:C}",
id.ToString(), name, salary);
}
}

  客户端代码:

    static void Main()
{
//构造函数指定了31个元素的容量。容量一般是素数。
//如果指定了一个不是素数的值,Dictionary<TKey,TValue>类会使用指定的整数后面紧接着的一个素数
var employees = new Dictionary<EmployeeId, Employee>(); var idTony = new EmployeeId("C3755");
var tony = new Employee(idTony, "Tony Stewart", 379025.00m);
employees.Add(idTony, tony);
Console.WriteLine(tony); var idCarl = new EmployeeId("F3547");
var carl = new Employee(idCarl, "Carl Edwards", 403466.00m);
employees.Add(idCarl, carl);
Console.WriteLine(carl); var idKevin = new EmployeeId("C3386");
var kevin = new Employee(idKevin, "Kevin Harwick", 415261.00m);
employees.Add(idKevin, kevin);
Console.WriteLine(kevin); var idMatt = new EmployeeId("F3323");
var matt = new Employee(idMatt, "Matt Kenseth", 1589390.00m);
employees[idMatt] = matt;
Console.WriteLine(matt); var idBrad = new EmployeeId("D3234");
var brad = new Employee(idBrad, "Brad Keselowski", 322295.00m);
employees[idBrad] = brad;
Console.WriteLine(brad);
}

3.Lookup类
  Dictionary<TKey,TValue>类支持每个键关联一个值。Lookup<TKey,TElement>类把键映射到一个值集上。这个类在程序集System.Core中实现,用System.Linq定义。

  Lookup<TKey,TElement>类不能像一般的字典那样创建,必须调用ToLookup()方法,该方法返回一个Lookup<TKey,TElement>对象。ToLookup()方法是一个扩展方法,它可以用于实现了IEnumerable<T>接口的所有类。
     ToLookup()方法需要一个Func<TSource,Tkey>,Func<TSource,Tkey>定义了选择器。

  

    static void Main()
{
var racers = new List<Racer>();
racers.Add(new Racer(, "Jacques", "Villeneuve", "Canada", ));
racers.Add(new Racer(, "Alan", "Jones", "Australia", ));
racers.Add(new Racer(, "Jackie", "Stewart", "United Kingdom", ));
racers.Add(new Racer(, "James", "Hunt", "United Kingdom", ));
racers.Add(new Racer(, "Jack", "Brabham", "Australia", ));
//国家相同的对象关联到一个键
var lookupRacers = racers.ToLookup(r => r.Country); foreach (Racer r in lookupRacers["Australia"])
{
Console.WriteLine(r);
} }

输出:
  Alan Jones
  Jack Brabham

4.有序字典
  SortedDictionary<TKey,TValue>类是一个二叉搜索树,其中的元素根据键来排序。该键类型必须实现IComparable<TKey>接口。
  如果键的类型不能排序,还可以创建一个实现了IComparer<TKey>接口的比较器,将比较器用作有序字典的构造函数的一个参数。
  SortedDictionary<TKey,TValue>和有序列表SortedList<TKey,TValue>(http://www.cnblogs.com/afei-24/p/6830376.html)的区别:
    *SortedList<TKey,TValue>类使用的内存比SortedDictionary<TKey,TValue>少。
    *SortedDictionary<TKey,TValue>元素的插入和删除操作比较快。
    *在用已排序好的数据填充集合时,若不需要改变容量,ortedList<TKey,TValue>比较快。

C#集合之字典的更多相关文章

  1. JavaScript数据结构——集合、字典和散列表

    集合.字典和散列表都可以存储不重复的值. 在集合中,我们感兴趣的是每个值本身,并把它当作主要元素.在字典和散列表中,我们用 [键,值] 的形式来存储数据. 集合(Set 类):[值,值]对,是一组由无 ...

  2. 使用JavaScriptSerializer序列化集合、字典、数组、DataTable为JSON字符串 分类: 前端 数据格式 JSON 2014-10-30 14:08 169人阅读 评论(0) 收藏

    一.JSON简介 JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的数据交换格式. JSON是"名值对"的集合.结构由大 ...

  3. python集合与字典的用法

    python集合与字典的用法 集合: 1.增加  add 2.删除   •del 删除集合 •discard(常用)删除集合中的元素  #删除一个不存在的元素不会报错 •remove 删除一个不存在的 ...

  4. 列表 list 容器类型数据(str字符串, list列表, tuple元组, set集合, dict字典)--->元组 tuple-->字符串 str

    # ### 列表 list 容器类型数据(str字符串, list列表, tuple元组, set集合, dict字典) # (1)定义一个列表 listvar = [] print(listvar, ...

  5. 5--Python入门--Python数据集合类型--字典

    列表list,最常用的数据类型,以[]为标识 元组tuple,和list很相似,但是不能二次赋值,用()标识 集合set,和list类似,但是set中没有重复的元素,常用于集合间的运算,用{}标识 字 ...

  6. Python 基础补充(一) 列表、元组、集合、字典的区别和相互转换

    一.列表.元组.集合.字典的区别   列表 元组 集合 字典 英文 list tuple set dict 可否读写 读写 只读 读写 读写 可否重复 是 是 否 是 存储方式 值 值 键(不能重复) ...

  7. [Swift]遍历集合类型(数组、集合和字典)

    Swift提供了三种主要的集合类型,称为数组,集合和字典,用于存储值集合. 数组是有序的值集合. 集是唯一值的无序集合. 字典是键值关联的无序集合. Swift中无法再使用传统形式的for循环. // ...

  8. Python的集合与字典练习

    集合与字典练习 question1 问题描述:有一个列表,其中包括 10 个元素,例如这个列表是[1,2,3,4,5,6,7,8,9,0],要求将列表中的每个元素一次向前移动一个位置,第一个元素到列表 ...

  9. Python——集合与字典练习

    集合与字典练习question1问题描述:有一个列表,其中包括 10 个元素,例如这个列表是[1,2,3,4,5,6,7,8,9,0],要求将列表中的每个元素一次向前移动一个位置,第一个元素到列表的最 ...

  10. 002-Python3-基础语法-赋值、显示类型、数据类型[数值、字符串、列表、元祖、集合、字典]

    一.基础语法 参看地址:https://www.runoob.com/python3/python3-tutorial.html 基础数据类型 Python 中的变量不需要声明.每个变量在使用前都必须 ...

随机推荐

  1. Android开发之AsyncTask示例Demo

    今天做了一个AsyncTask的小Demo,内含注释,通过此Demo,可以对AsyncTask有一个详细的了解 已经将项目上传到了GitHub上(程序有一个小bug,在第一次提交有说明,有解决方法请留 ...

  2. Android开发遇到短信备份失败

    今天做了一个有关ContentProvider的短信备份的小案例,遇到短信备份失败,费了一番周折后终于找到了问题所在 该案例是将短信写到一个xml文件然后保存在手机存储中实现短信的备份功能,关键实现代 ...

  3. canvas 3D雪花效果

    <!DOCTYPE html> <html style="height: 100%;"> <head> <meta charset=&qu ...

  4. Unity 3D Framework Designing(4)——设计可复用的SubView和SubViewModel(Part 2)

    在我们设计和开发应用程序时,经常要用到控件.比如开发一个客户端WinForm应用程序时,微软就为我们提供了若干控件,这些控件为我们提供了可被定制的属性和事件.属性可以更改它的外观,比如背景色,标题等, ...

  5. XML和JSON两种数据交换格式的比较

    在web开发领域,主要的数据交换格式有XML和JSON,对于在 Ajax开发中,是选择XML还是JSON,一直存在着争议,个人还是比较倾向于JSON的.一般都输出Json不输出xml,原因就是因为 x ...

  6. 【转】如何实现Flex页面跳转

    其实对于这个题目是不恰当的,因为flex中是没有页面这个概念的,页面在flex里面其实就是一个个的Canvas,vbox,hbox等等之类的东西,看到的不同页面的切换,就是这些元素一层层的堆积,或者替 ...

  7. 早期练手:功能相对比较完善的 js 计算器

    第一次发博客,就先发一个自己早期,刚开始学前端时,用js写的一个计算器吧,计算功能比较少,只有 + - * / ,不过其他功能还是比较完善的,比如: 输出结果后,连续按"=",可以 ...

  8. Linux开机启动(bootstrap)上

    Linux开机启动(bootstrap)   作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 计算机开机是一个神秘的过程.我们只是 ...

  9. Bootstrap基础学习(一)—表格与按钮

    一.Bootstrap 概述      Bootstrap 是由 Twitter 公司(全球最大的微博)的两名技术工程师研发的一个基于HTML.CSS.JavaScript 的开源框架.该框架代码简洁 ...

  10. Java基础学习(二)—数组

    一.数组的概念 定义: 数组是存储同一种数据类型的多个元素的集合. 数组既可以存储基本数据类型,也可以存储引用数据类型. 格式: 格式1: 数据类型[] 数组名; 格式2: 数据类型 数组名[]; 这 ...