C# 语言中的 Dictionary 和 ConcurrentDictionary你知道多少?

作者:微信公众号:【架构师老卢】
2-15 14:45
119

字典

C# 中的 A 是一个泛型集合,可用于存储键/值对,其中每个在字典中是唯一的,并且它映射到特定。Dictionary 类是作为哈希表实现的,因此提供了对基于键的值的快速访问。打个比方,你可以把字典想象成一个现实世界的字典,你在其中查找一个单词(键)来找到它的定义(值)。Dictionary

当您将键值对添加到字典时,它会根据键计算哈希码,该哈希码用于确定键值对在字典中的存储位置。正因为如此,使用 Key in a Dictionary 检索值的速度非常快——接近 O(1)。

Dictionary<TKey, TValue>class 允许您为键 () 和值 () 指定数据类型,例如,您可以创建一个整数字典作为键,将字符串创建为值(或任何其他数据类型),即:。Dictionary 键必须是唯一的,并且不应为 null(对于引用类型),而值可以为 null(如果它是引用类型)。TKeyTValueDictionary<int, string>

当您需要拥有集合并需要使用某些键(类)执行搜索时,字典是合适的。它可用于自定义数据结构、配置设置等任务,其中每个设置都由唯一键标识,用于将数据临时存储在内存中以供快速检索的简单缓存,需要通过唯一键快速检索的数据存储本地化,其中每个键代表一种语言中的术语或短语,并映射到另一种语言的相关翻译, 错误**处理和日志记录,**用于存储错误代码或异常详细信息以及相应的错误消息)。Dictionary<TKey, TValue>

字典示例

出于演示目的,我使用 .NET 7 创建了一个控制台应用程序,并将提供一些编码示例,说明如何在 C# 中使用字典。本例源代码在文末提供下载。

如何声明和初始化字典

为了便于演示,我将使用一个支付方式字典作为示例,该字典接收 a 作为 ,a 接收为 :integerKeystringValue

Dictionary<int, string> paymentMethods = new Dictionary<int, string>();

如何将键/值对添加到字典

为了将键值数据添加到字典中,您可以使用以下方法:Add

paymentMethods.Add(1, "Credit Card");  
paymentMethods.Add(2, "PayPal");  
paymentMethods.Add(3, "Google Pay");

如何安全地将键/值对添加到字典

如果尝试使用现有键添加其他项,则会引发错误。例如,使用前面添加 3 个项目的示例,如果我们尝试使用现有键添加新项目,将抛出以下错误:ArgumentException

paymentMethods.Add(3, "Cash");

输出将是一个例外:

Unhandled exception. System.ArgumentException: An item with the same key has already been added. Key: 3

为了避免这种情况,您可以使用该方法。如果字典中不存在键,此方法将添加该项,如果存在,则不会执行任何操作,也不会引发错误。例如:TryAdd

var cashAdded = paymentMethods.TryAdd(3, "Cash");  
var criptoCointAdded = paymentMethods.TryAdd(4, "Bitcoin");  
  
Console.WriteLine($"Was Cash added? {cashAdded}");  
Console.WriteLine($"Was Bitcoin added? {criptoCointAdded}");

输出将为:

Was Cash added? False
Was Bitcoin added? True

如何在字典中使用键和值进行迭代

若要检查字典中的项目,可以使用循环指令(或)来访问每个项目。例如:foreachLINQ

foreach (var paymentMethod in paymentMethods)  
{  
    Console.WriteLine($"Key: {paymentMethod.Key}, Value: {paymentMethod.Value}");  
}

输出将为:

Key: 1, Value: Credit Card
Key: 2, Value: PayPal
Key: 3, Value: Google Pay
Key: 4, Value: Bitcoin

如何专门循环访问键和值

如果只想使用键或仅使用值进行专门迭代,则可以使用 或 指定,例如:.Keys.Values

var keys = paymentMethods.Keys;  
  
foreach (var item in keys)  
{  
    Console.WriteLine($"{item}");  
}

与 Values 类似:

var values = paymentMethods.Values;  
  
foreach (var item in values)  
{  
    Console.WriteLine($"{item}");  
}

输出将为:

1
2
3
4
Credit Card
PayPal
Google Pay
Bitcoin

如何检索按键搜索的目标值

现在,假设您需要返回 Dictionary 中 Key number 2 中项目的目标值,为此您需要指定 Dictionary 和 Key 之间的 ,例如:[]

var paymentMethod = paymentMethods[keyToSearch];  
  
Console.WriteLine($"Payment Method with key {keyToSearch}: {paymentMethod}");

执行时,将具有值 ,因为这是包含此字典中键号 2 的项。这将是输出:

paymentMethodPayPal Payment Method with key 2: PayPal

当密钥不存在时会发生什么情况?

当您搜索字典中不存在的键时,将抛出 a。例如,下面的代码将导致以下错误消息:KeyNotFoundException

var paymentMethod = paymentMethods[5];

此操作的输出将为:

System.Collections.Generic.KeyNotFoundException: The given key '4' was not present in the dictionary.

如何按目标值检索键搜索

在字典中,您不能通过按目标值搜索来直接检索键,但是,您可以通过遍历字典并比较值来执行这样的搜索。在下面的示例中,使用 LINQ 完成了筛选,该筛选基于值筛选 Dictionary,然后选择包含该特定值的所有 Key。此搜索的结果将添加到变量中:List<int>

var valueToSearch = "PayPal";  
  
// Find keys by searching by the target value:  
var keysWithValue = paymentMethods  
    .Where(kv => kv.Value == valueToSearch)  
    .Select(kv => kv.Key)  
    .ToList();  
  
foreach (var key in keysWithValue)  
{  
    Console.WriteLine($"Key with value {valueToSearch}: {key}");  
}

输出将为:

Key with value PayPal: 2

请注意,在此示例中,结果可能是多个项目,因为 不是唯一的。如果您有多个特定值的项目,并且想要返回第一个项目,则可以使用 代替 .ValueFirstOrDefault()ToList()

如何安全地通过键进行目标值搜索

如果要搜索值并希望避免引发错误,可以使用以下方法:KeyNotFoundExceptionTryGetValue

if (paymentMethods.TryGetValue(keyToSearch, out string resultValue))  
{  
    Console.WriteLine($"Key {keyToSearch} was found. The target value is: {resultValue}");  
}  
else  
{  
    Console.WriteLine($"The Key {keyToSearch} was not found.");  
}

输出将为:

// For a non existent key:
The Key 5 was not found.

// For an existent key:
Key 4 was found. The target value is: Bitcoin

使用 LINQ 进行搜索

也可以利用 在字典中执行搜索。如果密钥不存在,则不会引发异常。LINQ

在下面的示例中,您可以了解如何使用 LINQ 按特定进行搜索。

var paymentMethod = paymentMethods  
    .Where(pair => pair.Key == keyToSearch)  
    .Select(pair => pair.Value)  
    .FirstOrDefault();

输出将为:

// For an existent key:
Payment method 2: PayPal

// For a non existent key:
Payment method 6 was not found

您也可以执行类似操作以按目标值进行搜索,在这种情况下,可能会返回多个结果(因为与键不同,目标不是唯一的)。例如:

var paymentMethod = paymentMethods  
    .Where(pair => pair.Key == keyToSearch)  
    .Select(pair => pair.Value)  
    .FirstOrDefault();  
  
if (paymentMethod != null)  
{  
    Console.WriteLine($"Payment method {keyToSearch}: {paymentMethod}");  
}  
else  
{  
    Console.WriteLine($"Payment method {keyToSearch} was not found");  
}

输出将为:

// When more than one key-value pair with target value "PayPal" exists:
Payment method(s) named 'PayPal' found with key(s): 2, 5

// When target value was not found in the dictionary:
Payment method 'Cash' was not found

检查是否存在包含的键或值

如果需要检查字典中是否存在 Key 或 Value,可以使用返回布尔值的 和 方法:ContainsKeyContainsValue

var containsKey1 = paymentMethods.ContainsKey(1);  
var containsKey5 = paymentMethods.ContainsKey(5);  
Console.WriteLine($"Contains Key 1: {containsKey1}");  
Console.WriteLine($"Contains Key 5: {containsKey5}");  
  
var containsValuePayPal = paymentMethods.ContainsValue("PayPal");  
var containsValueCash = paymentMethods.ContainsValue("Cash");  
Console.WriteLine($"Contains Value PayPal: {containsValuePayPal}");  
Console.WriteLine($"Contains Value Cash: {containsValueCash}");

输出将为:

Contains Key 1: True
Contains Key 5: False
Contains Value PayPal: True
Contains Value Cash: False

如何更新字典中键的值?

假设现在我们需要更新字典中 Key number 3 的值,您可以通过传递 Key 和 new Value 轻松更新它:

var key = 3;  
  
Console.WriteLine($"Value of Key 3 before the update: {paymentMethods[key]}");  
  
// Update the value of the item with Key number 3:  
paymentMethods[key] = "Apple Pay";  
  
Console.WriteLine($"Value of Key {key} after the update: {paymentMethods[key]}");

输出将为:

Value of Key 3 before the update: Google Pay
Value of Key 3 after the update: Apple Pay

如何更新字典中的键?

字典中的键是不可变的(因为它们用于创建哈希值,从而确保有效的查找和检索),这意味着我们无法更新现有的键。但是,您可以通过添加新的键值对并删除前一个键值对来实现类似的“更新”效果:

var keyToBeRecriated = 3;  
var newKeyToBeCreated = 5;  
  
if (paymentMethods.ContainsKey(keyToBeRecriated))  
{  
    // Step 1: Add a new key-value pair with the updated key:  
    paymentMethods[newKeyToBeCreated] = paymentMethods[keyToBeRecriated];  
  
    // Step 2: Remove the old key:  
    paymentMethods.Remove(keyToBeRecriated);  
}  
  
// Display the updated dictionary  
foreach (var paymentMethod in paymentMethods)  
{  
    Console.WriteLine($"{paymentMethod.Key}: {paymentMethod.Value}");  
}

在此示例中,删除了密钥编号为 3 的项目(“Apple Pay”),并在字典中添加了一个新项目,密钥编号为 4,值为“Apple Pay”。输出将为:

Key: 1, Value: Credit Card
Key: 2, Value: PayPal
Key: 4, Value: Apple Pay

如何检查字典中存在多少个项目

如果需要检查字典中的项数,可以使用以下方法:CountLINQ

var dictionarySize = paymentMethods.Count();  
  
Console.WriteLine($"Dictionary size: {dictionarySize}");

输出将为:

Dictionary size: 4

如何删除词典中的项目

要删除字典中的项目,我们可以使用 .如果 Key 存在于字典中,此方法将删除键值对,如果该键不存在,则它不会执行任何操作。Remove

// Remove an item based on the Key:  
paymentMethods.Remove(keyToBeRemoved);  
  
// or  
  
// Which returns a boolean value (true when item is removed, and false when is not removed):  
var wasRemoved = paymentMethods.Remove(keyToBeRemoved);

ConcurrentDictionary (并发词典)

A 是键/值对的线程安全集合,可由多个线程并发访问,并确保数据完整性。此类旨在处理来自多个线程的并发访问,从而避免争用条件 — 当两个或多个线程同时访问和修改共享数据或资源时,可能会出现争用条件,从而导致不可预知的行为,例如数据损坏、不正确的结果、异常等。当您需要可由多个线程同时访问的键/值对的线程安全集合时,可以使用该类。ConcurrentDictionaryConcurrentDictionary<TKey,TValue>

“通过 ConcurrentDictionary<TKey,TValue> 实现的接口之一访问的成员(包括扩展方法)不能保证是线程安全的,可能需要由调用方同步。”(Microsoft 文档)

您可以像这样初始化 ConcurrentDictionary:

ConcurrentDictionary<int, string> paymentMethodsConcurrentDictionary = new ConcurrentDictionary<int, string>();

如何在 ConcurrentDictionary 中添加项

与将项添加到 Dictionary 中类似,若要将项添加到 ConcurrentDictionary 中,可以使用该方法。TryAdd

如何在 ConcurrentDictionary 中搜索项目

要在 ConcurrentDictionary 中获取键值对,您可以使用 .此方法将根据 Key 搜索项目,并将返回一个布尔值(找到项目时为 true,未找到项目时为 false),而不会在密钥不存在时引发异常。例如:TryGetMethod

var existentKey = 2;  
  
if (paymentMethodsConcurrentDictionary.TryGetValue(existentKey, out string valueForExistingKey))  
{  
    Console.WriteLine($"Key {existentKey} was found. Value: {valueForExistingKey}");  
}  
else  
{  
    Console.WriteLine($"Key {existentKey} was not found");  
}  
  
var nonExistentKey = 6;  
  
if (paymentMethodsConcurrentDictionary.TryGetValue(nonExistentKey, out string valueForNonExistingKey))  
{  
    Console.WriteLine($"Updated Value for {nonExistentKey}: {valueForNonExistingKey}");  
}  
else  
{  
    Console.WriteLine($"Key {nonExistentKey} was not found\\n");  
}

输出将为:

The Key 2 was removed. Value: PayPal
The Key 6 was not found

如果需要搜索目标,可以按照与 Dictionary 示例相同的方法使用 。LINQ

如何在 ConcurrentDictionary 中添加或更新项

要添加新的键值对或更新现有项的目标值,可以使用该方法或。该方法的工作方式与之前介绍的普通字典类似,因此我将重点讨论该方法的这个主题。此方法接收三个参数:TryAddAddOrUpdateTryAddAddOrUpdate

  • 1st:要添加/更新的密钥
  • 第二个参数:在字典中不存在键时要添加的值
  • 第三:一个函数,在键已存在的情况下返回要在字典中更新的值。此函数采用两个参数:键和一个值。

这是如何使用该方法的示例。在第一个代码块中,将更新一个现有值,在第二个代码块中,将添加一个新的键值对:AddOrUpdate

// In this example, the key-value pair with Key 3 will be updated:  
var keyToBeUpdated = 3;  
paymentMethodsConcurrentDictionary.AddOrUpdate(  
    keyToBeUpdated,  
    "Apple Pay (Added)",     // Value to add if the the key does not exist;  
    (key, oldValue) => "Apple Pay (Updated)"  // Update the existing value;  
);  
  
// In this example, a new key-value pair with Key 4 will be added:  
var keyToBeAdded = 4;  
paymentMethodsConcurrentDictionary.AddOrUpdate(  
    keyToBeAdded,  
    "Cash (Added)",     // Value to add since the key does not exist;  
    (key, oldValue) => oldValue  // No update is performed in this case;  
);  
  
// Display the updated dictionary:  
foreach (var keyValuePair in paymentMethodsConcurrentDictionary)  
{  
    Console.WriteLine($"Key: {keyValuePair.Key}, Value: {keyValuePair.Value}");  
}

输出将为:

Key: 1, Value: Credit Card
Key: 2, Value: PayPal
Key: 3, Value: Apple Pay (Updated)
Key: 4, Value: Cash (Added)

如何删除 ConcurrentDictionary 中的键值对

为了删除 ConcurrentDictionary 中的项目,您可以使用:TryRemove

var keyRemoved = paymentMethodsConcurrentDictionary.TryRemove(keyToBeRemoved, out string removedValue);  
  
if (keyRemoved)  
{  
    Console.WriteLine($"The Key {keyToBeRemoved} was removed. Value: {removedValue}");  
}  
else  
{  
    Console.WriteLine($"The Key {keyToBeRemoved} was not found.");  
}

输出将为:

// For an existent key:
The Key 2 was removed. Value: PayPal
// For a non existent key:
The Key 6 was not found.

结论

A 是 C# 中一种高效的数据结构,它提供了一种灵活而强大的方法来高效地管理键值对。Dictionary 类是作为哈希表实现的,允许您快速访问基于键的值。您可以将其用于单线程方案和多线程方案,从而避免争用条件。本文介绍了如何创建和使用字典,并使用此数据结构执行一系列操作。DictionaryDictionaryConcurrentDictionary

源代码获取:公众号回复消息【code:82994】

相关代码下载地址
重要提示!:取消关注公众号后将无法再启用回复功能,不支持解封!
第一步:微信扫码关键公众号“架构师老卢”
第二步:在公众号聊天框发送code:82994,如:code:82994 获取下载地址
第三步:恭喜你,快去下载你想要的资源吧
相关留言评论
昵称:
邮箱:
阅读排行