zoukankan      html  css  js  c++  java
  • C#集合之字典

      字典表示一种复杂的数据结构,这种数据结构允许按照某个键来访问元素。字典也称为映射或散列表。
      字典的主要特性是能根据键快速查找值。也可以自由添加和删除元素,这有点像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())[0];
                  int numLength = id.Length - 1;
                  try
                  {
                    number = int.Parse(id.Substring(1, numLength > 6 ? 6 : 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 << 16) * 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>(31);
    
              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(26, "Jacques", "Villeneuve", "Canada", 11));
              racers.Add(new Racer(18, "Alan", "Jones", "Australia", 12));
              racers.Add(new Racer(11, "Jackie", "Stewart", "United Kingdom", 27));
              racers.Add(new Racer(15, "James", "Hunt", "United Kingdom", 10));
              racers.Add(new Racer(5, "Jack", "Brabham", "Australia", 14));
              //国家相同的对象关联到一个键
              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>比较快。

  • 相关阅读:
    托付和事件的使用
    在使用supervisord 管理tomcat时遇到的小问题
    无法安装vmware tools的解决方PLEASE WAIT! VMware Tools is currently being installed on your system. Dependin
    (转)Openlayers 2.X加载高德地图
    (转)openlayers实现在线编辑
    (转) Arcgis for js加载百度地图
    (转)Arcgis for js加载天地图
    (转) 基于Arcgis for Js的web GIS数据在线采集简介
    (转) Arcgis for js之WKT和GEOMETRY的相互转换
    (转)Arcgis for Js之Graphiclayer扩展详解
  • 原文地址:https://www.cnblogs.com/afei-24/p/6835222.html
Copyright © 2011-2022 走看看