字典
- 又称映射或散列表
- 特点:根据键快速查找值
- 能自由地添加和删除元素,但内存中不需移动后续元素
- 原理:
- 键会转换为一个散列
- 利用散列,创建一个数字,它将索引和值,关联起来
- 索引包含一个到值的链接
- 索引可以存储为一个树形结构
- 一个索引,可以关联多个值
- .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 Johnson ¥150,926.00
F000022: Joey Logano ¥45,125.00
T000018: Kyle Bush ¥78,728.00
T000019: Carl Edwards ¥80,473.00
T000020: Matt Kenseth ¥113,970.00
Enter employee id (X to exit)> T19
T000019: Carl Edwards ¥80,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 Johnson ¥150,926.00
Enter employee id (X to exit)> F22
F000022: Joey Logano ¥45,125.00
Enter employee id (X to exit)>