c# 高级编程 10章219页 【集合】【字典】【Dictionary<TKey, TValue>】

229 阅读4分钟

字典

  • 又称映射散列表
  • 特点:根据快速查找值
  • 能自由地添加和删除元素,但内存中不需移动后续元素
  • 原理:
    • 会转换为一个散列
    • 利用散列,创建一个数字,它将索引和值关联起来
    • 索引包含一个到的链接
      • 索引可以存储为一个树形结构
      • 一个索引,可以关联多个值

image.png

  • .NET Framework提供几个字典类:
    • Dictionary<TKey, TValue>

Dictionary<TKey, TValue>的初始化器

  • 可以声明时初始化
var dict = new Dictionary<int, string>()
{
    [3] = "three",
    [7] = "seven"
};

Dictionary<TKey, TValue>的键

字典中键的类型必须重写Object类的GetHashCode()

  • GetHashCode()返回的int, 用于计算索引,用于确定元素的位置
  • 计算索引的算法,涉及素数
  • GetHashCode()必须满足以下条件:
    • 相同的对象总是返回相同的值
    • 不同的对象可以返回相同的值
    • 不能抛出异常
    • 它应至少使用一个实例字段
    • 散列代码最好在对象的生存期中不发生变化
  • GetHashCode()最好满足以下条件:
    • 执行的比较快,计算开销不大
    • 散列代码值平均分布在int可以存储的整个数字范围上。为啥?因为如果两个键计算得到的散列代码值会得到相同的索引,那就必须经过一番搜索来寻找最近的可用空闲位置来存储第二个数据项。这显然会降低性能。平均分布,会降低冲突出现的可能。

字典中键的类型必须实现IEquatable<T>.Equals()方法,或者重写Object类的Equals()

  • 因为不同的键,可能返回相同的散列代码值,所以需要用Equals()方法来比较键,A.Equals(B)
  • 必须始终确保如下条件成立: 如果A.Equals(B)为true, 那么A.GetHashCode()和B.GetHashCode()总是返回相同的散列代码值。这个条件成立很重要,如果用作键的类型不满足此条件,字典就不能正常工作,会发生有趣的事情,例如放进字典后就再也检索不到它,或者检索得到错误的项。

System.Object作为字典的键

  • Equals(),比较引用
  • GetHashCode(), 返回一个基于对象地址的散列代码值

类实例作为key,而放入字典的value时,类实例的引用和value建立了一一对应的关系,若想检索value, 就必须保存着类实例的引用,这不太方便要想方便,这个类需要重写Equals()GetHashCode()

System.String作为字典的键

  • 实现了IEquatable接口,Equals()比较值
  • GetHashCode(),返回一个基于字符串值的散列代码值
  • System.String作为字典的键,很方便

Int32作为字典的键

  • 实现了IEquatable接口
  • GetHashCode()
  • Int32作为键,其散列代码值可能没有平均分布在int.MinValue和int.MaxValue之间,因此,不能获得最佳性能因此,Int32不太适合作为键

如果键类型没有实现IEquatable接口,也没有重写GetHashCode()方法

  • Dictionary<TKey, TValue> 构造函数的重载版本,接收一个IEqualityComparer<T>
  • IEqualityComparer<T>定义了GetHashCode()Equals()

示例

    public class Employee
    {
        private readonly string _name;
        private readonly decimal _salary;
        private readonly EmployeeId _id;

        public Employee(EmployeeId id, string name, decimal salary)
        {
            _id = id;
            _name = name;
            _salary = salary;
        }

        public override string ToString() => $"{_id.ToString()}: {_name, -20} {_salary :C}";
    }
    public class EmployeeIdException : Exception
    {
        public EmployeeIdException(string message) : base(message) { }
    }

    public struct EmployeeId : IEquatable<EmployeeId>
    {
        private readonly char _prefix;
        private readonly int _number;

        public EmployeeId(string id)
        {
            if (id == null) throw new ArgumentNullException(nameof(id));

            _prefix = (id.ToUpper())[0];
            int numLength = id.Length - 1;
            try
            {
                _number = int.Parse(id.Substring(1, numLength > 6 ? 6 : numLength));
            }
            catch (FormatException)
            {
                throw new EmployeeIdException("Invalid EmployeeId format");
            }
        }

        public override string ToString() => _prefix.ToString() + $"{_number,6:000000}";

        public override int GetHashCode() => (_number ^ _number << 16) * 0x1505_1505;

        public bool Equals(EmployeeId other) => _prefix == other._prefix && _number == other._number;

        public override bool Equals(object obj) => Equals((EmployeeId)obj);

        public static bool operator ==(EmployeeId left, EmployeeId right) => left.Equals(right);

        public static bool operator !=(EmployeeId left, EmployeeId right) => !(left == right);
    }
    class Program
    {
        static void Main()
        {
            var idJimmie = new EmployeeId("C48");
            var jimmie = new Employee(idJimmie, "Jimmie Johnson", 150926.00m);

            var idJoey = new EmployeeId("F22");
            var joey = new Employee(idJoey, "Joey Logano", 45125.00m);

            var idKyle = new EmployeeId("T18");
            var kyle = new Employee(idKyle, "Kyle Bush", 78728.00m);

            var idCarl = new EmployeeId("T19");
            var carl = new Employee(idCarl, "Carl Edwards", 80473.00m);

            var idMatt = new EmployeeId("T20");
            var matt = new Employee(idMatt, "Matt Kenseth", 113970.00m);

            var employees = new Dictionary<EmployeeId, Employee>(31)
            {
                [idJimmie] = jimmie,
                [idJoey] = joey,
                [idKyle] = kyle,
                [idCarl] = carl,
                [idMatt] = matt
            };

            foreach (var employee in employees.Values)
            {
                Console.WriteLine(employee);
            }

            while (true)
            {
                Console.Write("Enter employee id (X to exit)> ");
                var userInput = Console.ReadLine();
                userInput = userInput.ToUpper();
                if (userInput == "X") break;

                EmployeeId id;
                try
                {
                    id = new EmployeeId(userInput);

                    if (!employees.TryGetValue(id, out Employee employee))
                    {
                        Console.WriteLine($"Employee with id {id} does not exist");
                    }
                    else
                    {
                        Console.WriteLine(employee);
                    }
                }
                catch (EmployeeIdException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }

输出:(存入字典的数据项都能用键给检索出来)

C000048: Jimmie Johnson150,926.00
F000022: Joey Logano45,125.00
T000018: Kyle Bush78,728.00
T000019: Carl Edwards80,473.00
T000020: Matt Kenseth113,970.00
Enter employee id (X to exit)> T19
T000019: Carl Edwards80,473.00
Enter employee id (X to exit)> 148
Employee with id 1000048 does not exist
Enter employee id (X to exit)> T48
Employee with id T000048 does not exist
Enter employee id (X to exit)> C48
C000048: Jimmie Johnson150,926.00
Enter employee id (X to exit)> F22
F000022: Joey Logano45,125.00
Enter employee id (X to exit)>