C# Dictionary->ConcurrentDictionary和哈希表

2024-08-20 20:04

本文主要是介绍C# Dictionary->ConcurrentDictionary和哈希表,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、C# Dictionary

C# 中的 Dictionary 是一个用于存储键值对的集合。每个键必须是唯一的,且每个键对应一个值。Dictionary 提供了快速查找、添加和删除键值对的功能。

基本用法如下:

// 创建一个 Dictionary
Dictionary<int, string> dictionary = new Dictionary<int, string>();// 添加键值对
dictionary.Add(1, "Value1");
dictionary.Add(2, "Value2");// 访问值
string value1 = dictionary[1];// 修改值
dictionary[1] = "NewValue1";// 检查是否包含键
bool hasKey = dictionary.ContainsKey(1);// 删除键值对
dictionary.Remove(1);// 遍历 Dictionary
foreach (var kvp in dictionary)
{Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
  • 键类型 (TKey) 和值类型 (TValue)Dictionary<TKey, TValue> 中,TKey 是键的类型,TValue 是值的类型。
  • 访问速度Dictionary 使用哈希表实现,平均情况下可以在 O(1) 时间复杂度下访问元素。Dictionary 常用于需要快速查找、添加或删除元素的场景。

二、为什么说Dictionary类型的安全性和性能比较好呢

Dictionary 类型在 C# 中被认为具有良好的安全性和性能,主要原因如下:

  • 1. 类型安全性

C# 的 Dictionary<TKey, TValue> 是一个强类型的数据结构,这意味着它在编译时就对键和值的类型进行了检查。由于类型是明确的,编译器可以在编译时发现类型不匹配的问题,从而减少了运行时错误的可能性。这种类型安全性提高了代码的可靠性和可维护性。

例如,创建一个 Dictionary<int, string> 后,键必须是 int 类型,值必须是 string 类型,如果尝试存储其他类型的数据,编译器会报错。

Dictionary<int, string> dictionary = new Dictionary<int, string>();
dictionary.Add(1, "Value"); // 正确
// dictionary.Add("key", 2); // 错误:键和值的类型不匹配

2. 性能优越性

Dictionary 采用了哈希表作为其底层数据结构,能够在平均情况下实现 O(1) 的查找、插入和删除操作。这使得 Dictionary 在处理大量数据时具有很高的性能。

  • 哈希表查找速度快Dictionary 使用键的哈希码来快速定位值的位置,这使得查找、插入和删除操作的时间复杂度非常低,通常接近于 O(1)。
  • 自动扩展Dictionary 会根据元素的数量自动调整内部哈希表的大小,以保持高效的操作性能。这种动态调整使得 Dictionary 能够在不同规模的数据集下都保持良好的性能。

3. 避免重复键

Dictionary 中的键是唯一的,如果尝试添加一个已经存在的键,会抛出异常。这种设计有效防止了数据的重复和混乱,提高了数据的完整性。

4. 线程安全性

虽然 Dictionary 本身不是线程安全的,但在单线程场景中,Dictionary 提供了良好的性能和安全性。如果需要在多线程环境中使用,可以考虑使用 ConcurrentDictionary,它是线程安全的版本,适用于并发操作。

综上所述,Dictionary 的类型安全性、哈希表带来的高效查找、插入和删除操作,以及防止重复键的机制,都是其安全性和性能优越的原因。

三、什么是哈希表

哈希表(Hash Table)是一种数据结构,用于高效地存储和检索数据。它通过将数据的键(Key)映射到表中的一个位置(索引)来实现快速的查找操作。这种映射通常是通过一个称为哈希函数(Hash Function)的算法来完成的。

哈希表的工作原理

  1. 哈希函数:哈希表的核心是哈希函数。哈希函数接受一个键作为输入,并输出一个整数,这个整数称为哈希值(Hash Value)。哈希值通常用于确定数据在哈希表中的存储位置(即数组索引)。一个好的哈希函数应该能将输入的键均匀地分布在哈希表的不同位置上,以减少冲突(collisions)。

Hash Function: Key -> Hash Value
  1. 存储数据:当你向哈希表中插入一个键值对时,哈希函数会计算出这个键的哈希值,然后将值存储在哈希表中该哈希值对应的位置上。

  2. 冲突处理:由于哈希表的空间是有限的,两个不同的键可能会产生相同的哈希值,这种情况称为冲突(Collision)。处理冲突的方法主要有以下几种:

    • 链地址法(Separate Chaining):在哈希表的每个索引处存储一个链表,所有哈希到同一位置的键值对都存储在该链表中。
    • 开放地址法(Open Addressing):当发生冲突时,通过探查其他空闲的位置来存储冲突的键值对。
  3. 查找数据:查找操作首先通过哈希函数计算出键的哈希值,然后直接跳转到对应位置进行查找。如果哈希表中存在该键,就可以直接返回相应的值,否则需要处理冲突的情况。

哈希表的优点

  • 快速查找、插入和删除:哈希表的平均查找、插入和删除操作的时间复杂度是 O(1),这意味着操作速度非常快,尤其适合需要频繁访问的场景。
  • 灵活性:哈希表可以存储任意类型的键值对,适用于各种应用场景。

哈希表的缺点

  • 空间利用率:哈希表通常需要为避免冲突预留一定的空闲空间,因此空间利用率可能较低。
  • 处理冲突的复杂性:尽管有多种方法处理冲突,但冲突处理仍可能导致性能下降,特别是在哈希表负载因子(即元素数量与哈希表大小的比值)较高时。

应用场景

哈希表在实际应用中非常广泛,例如:

  • 数据库索引
  • 缓存(如 LRU 缓存)
  • 字典/映射(如 C# 中的 Dictionary、Java 中的 HashMap
  • 集合操作(如判断集合中的元素是否存在)

总的来说,哈希表是一种高效且灵活的数据结构,适用于需要快速查找和操作大量数据的场景。

四、ConcurrentDictionary使用场景

ConcurrentDictionary 是 C# 中一种线程安全的字典实现,适用于多线程环境下需要频繁访问和操作共享数据的场景。它在设计时考虑了并发操作,通过对内部数据结构的分段锁定和其他优化手段,确保在多线程环境下能够高效、安全地进行读写操作。

ConcurrentDictionary 的主要使用场景:

  1. 多线程数据共享

    • 在多线程程序中,多个线程可能需要同时读取或更新同一个数据集合。使用普通的 Dictionary 可能会导致数据竞争问题,如死锁、数据损坏等。而 ConcurrentDictionary 提供了线程安全的操作,使多个线程可以安全地并发读取和更新数据。
    • 示例:多个线程同时处理不同的任务,并需要共享某些状态数据,如任务进度或统计信息。
  2. 频繁读写的缓存

    • 当你需要在多线程环境中实现一个缓存,并且缓存的数据可能被频繁更新时,ConcurrentDictionary 是一个理想的选择。它允许多个线程同时读取缓存,同时确保写操作的原子性。
    • 示例:在一个高并发的Web应用中,使用 ConcurrentDictionary 来缓存用户会话数据或其他经常访问但不常变化的数据。
  3. 计数器或计数统计

    • 在多线程环境中统计事件的发生频率,如记录某个事件在一段时间内发生的次数。ConcurrentDictionary 可以安全地管理这些计数器,即使多个线程同时更新计数也不会出现问题。
    • 示例:Web服务器中统计不同类型请求的数量,或在日志系统中记录每种日志级别的出现次数。
  4. 多线程集合操作

    • 当你需要在多线程环境中管理一组对象,并且这些对象可能频繁地被添加或删除时,ConcurrentDictionary 提供了一个安全、高效的选择。
    • 示例:在游戏服务器中管理在线玩家的列表,多个线程可能会同时添加或移除玩家。
  5. 延迟初始化或单例模式

    • 在某些情况下,你可能希望某些资源(如数据库连接、配置对象等)在首次访问时才初始化,并且初始化过程可能需要线程安全的操作。ConcurrentDictionary 可以用来实现延迟初始化,确保资源只被初始化一次。
    • 示例:在一个多线程应用中,每个线程可能需要访问某种服务的实例,而这些实例应当在首次访问时创建并缓存。

ConcurrentDictionary 的优势:

  • 高效的并发处理:通过细粒度的锁机制,它能够在大多数情况下实现比全局锁定更好的性能。
  • 线程安全:内置了对并发访问的处理,避免了手动加锁的复杂性和容易出错的问题。
  • 易用性:提供了丰富的 API,如 TryAddGetOrAddAddOrUpdate 等,可以简化常见的并发操作。

总的来说,ConcurrentDictionary 适用于任何需要在多线程环境下安全、高效地管理键值对集合的场景。它简化了多线程编程中的数据管理,使开发者能够专注于业务逻辑,而不必担心数据一致性和竞争问题。

五、ReadOnlyDictionary

ReadOnlyDictionary 是 C# 中用于表示只读字典的类。它包装了一个可变的字典,使得外部无法修改其内容。这个类非常适合用于需要提供不可变集合的场景,即确保字典中的数据在初始化之后不会被修改。

ReadOnlyDictionary 的特点

  • 不可变性:一旦 ReadOnlyDictionary 被创建,任何对字典的修改(如添加、删除或更新键值对)都会被禁止。
  • 线程安全性:由于其不可变性,在多线程环境下使用 ReadOnlyDictionary 可以避免由于数据修改引发的线程安全问题。
  • 包装现有字典ReadOnlyDictionary 实际上是对现有可变字典的包装。因此,如果底层字典被修改,ReadOnlyDictionary 的内容也会反映这些变化。要真正实现不可变性,底层字典也应该是不可变的。

使用场景

  • 返回值保护:当一个方法返回字典时,如果你不希望调用者能够修改这个字典,可以将其转换为 ReadOnlyDictionary
  • API 设计:在设计公共 API 时,如果某些数据应该是只读的,使用 ReadOnlyDictionary 可以明确表达这一意图。
  • 防止意外修改:在某些情况下,你可能希望确保字典的内容不会被意外修改,使用 ReadOnlyDictionary 可以防止此类问题。

示例代码

以下是如何使用 ReadOnlyDictionary 的示例:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;class Program
{static void Main(){// 创建一个可变字典Dictionary<int, string> mutableDictionary = new Dictionary<int, string>{{ 1, "One" },{ 2, "Two" },{ 3, "Three" }};// 创建一个只读字典ReadOnlyDictionary<int, string> readOnlyDictionary = new ReadOnlyDictionary<int, string>(mutableDictionary);// 尝试读取数据Console.WriteLine(readOnlyDictionary[1]); // 输出: One// 尝试修改数据(这将引发编译错误)// readOnlyDictionary[1] = "OneModified"; // 编译错误: ReadOnlyDictionary 不允许修改操作// 尝试添加新元素(这也会引发编译错误)// readOnlyDictionary.Add(4, "Four"); // 编译错误: ReadOnlyDictionary 不允许添加操作// 注意:如果修改底层的可变字典,ReadOnlyDictionary 也会反映这些变化mutableDictionary[1] = "OneModified";Console.WriteLine(readOnlyDictionary[1]); // 输出: OneModified}
}

注意事项

  • 底层字典的可变性ReadOnlyDictionary 只阻止通过 ReadOnlyDictionary 本身的修改操作。如果底层字典仍然是可变的,那么通过底层字典的修改仍然会影响到 ReadOnlyDictionary
  • 不可变性保证:如果真正需要保证数据的不可变性,最好在创建 ReadOnlyDictionary 之前使用不可变的字典结构,或者确保底层字典在创建后不会再被修改。

总的来说,ReadOnlyDictionary 在需要提供只读视图或者防止数据被意外修改的场景下非常有用

这篇关于C# Dictionary->ConcurrentDictionary和哈希表的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1091051

相关文章

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

哈希表的底层实现(1)---C++版

目录 哈希表的基本原理 哈希表的优点 哈希表的缺点 应用场景 闭散列法 开散列法 开放定值法Open Addressing——线性探测的模拟实现 超大重点部分评析 链地址法Separate Chaining——哈希桶的模拟实现 哈希表(Hash Table)是一种数据结构,它通过将键(Key)映射到值(Value)的方式来实现快速的数据存储与查找。哈希表的核心概念是哈希

C# dateTimePicker 显示年月日,时分秒

dateTimePicker默认只显示日期,如果需要显示年月日,时分秒,只需要以下两步: 1.dateTimePicker1.Format = DateTimePickerFormat.Time 2.dateTimePicker1.CustomFormat = yyyy-MM-dd HH:mm:ss Tips:  a. dateTimePicker1.ShowUpDown = t

C#关闭指定时间段的Excel进程的方法

private DateTime beforeTime;            //Excel启动之前时间          private DateTime afterTime;               //Excel启动之后时间          //举例          beforeTime = DateTime.Now;          Excel.Applicat

C# 防止按钮botton重复“点击”的方法

在使用C#的按钮控件的时候,经常我们想如果出现了多次点击的时候只让其在执行的时候只响应一次。这个时候很多人可能会想到使用Enable=false, 但是实际情况是还是会被多次触发,因为C#采用的是消息队列机制,这个时候我们只需要在Enable = true 之前加一句 Application.DoEvents();就能达到防止重复点击的问题。 private void btnGenerateSh

C# double[] 和Matlab数组MWArray[]转换

C# double[] 转换成MWArray[], 直接赋值就行             MWNumericArray[] ma = new MWNumericArray[4];             double[] dT = new double[] { 0 };             double[] dT1 = new double[] { 0,2 };