C# 中的 `Hashtable`

今天我们来详细解释一下 C# 中的 Hashtable

1. 什么是 Hashtable?

Hashtable 是 .NET Framework 中的一个集合类,位于 System.Collections 命名空间。它代表了一个键值对的集合,这些键值对根据键的哈希代码进行组织。

核心特性:

  • 键值对存储 :每个元素都是一个 DictionaryEntry 对象,包含一个键(Key)和一个值(Value)。
  • 哈希算法:通过键的哈希码来快速定位数据,从而实现高效的检索。
  • 非泛型Hashtable 是在 .NET 1.0 时期引入的非泛型集合 。这意味着它存储的键和值都是 object 类型。
  • 动态扩容:当元素数量达到某个阈值时,它会自动增加容量(即内部数组的大小)。

2. 核心工作原理

理解 Hashtable 的关键在于理解其哈希机制冲突解决

a. 哈希函数

当你添加一个键值对时,Hashtable 会调用键对象的 GetHashCode() 方法来计算其哈希码。这个哈希码是一个数字,用于确定该键值对在 Hashtable 内部数组(通常称为"桶"或"buckets")中的初始存储位置。

GetHashCode() 方法需要满足以下条件,才能保证 Hashtable 高效工作:

  • 一致性 :如果两个对象相等(根据 Equals 方法),那么它们必须返回相同的哈希码。
  • 高效性:计算应该非常快。
  • 分布均匀:不同的对象应尽可能产生不同的哈希码,以减少冲突。

b. 处理哈希冲突

不同的键有可能计算出相同的哈希码,或者不同的哈希码被映射到同一个内部数组索引上,这种情况称为哈希冲突Hashtable 通过以下两种主要方法之一来解决冲突:

  1. 拉链法 :这是最常用的方法。每个"桶"不再存储单个元素,而是存储一个链表(或其他数据结构)。当发生冲突时,新的键值对会被添加到对应桶的链表中。在查找时,会先定位到桶,然后在链表中进行线性搜索,使用 Equals 方法来比较键。
  2. 开放地址法:当发生冲突时,它会按照某种探测规律(如线性探测、二次探测)在数组中寻找下一个空闲的位置。

3. 主要特性

  • 键必须是唯一的 :尝试添加具有相同键的元素会抛出 ArgumentException
  • 键不能为 null :尝试使用 null 作为键会抛出 ArgumentNullException
  • 值可以为 null :允许将 null 作为值存储。
  • 元素是无序的:不能保证键值对的遍历顺序与插入顺序相同。顺序可能会因扩容和重新哈希而改变。
  • 线程安全Hashtable 通过 Synchronized 方法提供了一种线程安全的包装器。但在现代编程中,更推荐使用 ConcurrentDictionary

4. 基本用法(代码示例)

csharp 复制代码
using System;
using System.Collections; // 必须引入此命名空间

class Program
{
    static void Main()
    {
        // 1. 创建 Hashtable
        Hashtable myHashtable = new Hashtable();

        // 2. 添加元素
        myHashtable.Add("name", "Alice");
        myHashtable.Add("age", 30);
        myHashtable.Add(1, "Number One"); // 键可以是不同类型,但不推荐
        myHashtable["city"] = "New York"; // 使用索引器添加/修改

        // 3. 访问元素
        string name = (string)myHashtable["name"]; // 需要显式类型转换
        Console.WriteLine($"Name: {name}"); // 输出:Name: Alice

        // 使用索引器修改值
        myHashtable["age"] = 31;

        // 4. 检查键是否存在
        if (myHashtable.ContainsKey("city"))
        {
            Console.WriteLine($"City: {myHashtable["city"]}");
        }

        // 5. 遍历 Hashtable
        Console.WriteLine("\n--- 遍历所有键值对 ---");
        foreach (DictionaryEntry de in myHashtable)
        {
            Console.WriteLine($"Key: {de.Key}, Value: {de.Value}, Type: {de.Value.GetType()}");
        }

        Console.WriteLine("\n--- 单独遍历所有键 ---");
        foreach (var key in myHashtable.Keys)
        {
            Console.WriteLine($"Key: {key}");
        }

        Console.WriteLine("\n--- 单独遍历所有值 ---");
        foreach (var value in myHashtable.Values)
        {
            Console.WriteLine($"Value: {value}");
        }

        // 6. 移除元素
        myHashtable.Remove("age"); // 移除键为 "age" 的项

        // 7. 清空
        // myHashtable.Clear();
    }
}

输出示例(顺序可能不同):

复制代码
Name: Alice
City: New York

--- 遍历所有键值对 ---
Key: name, Value: Alice, Type: System.String
Key: city, Value: New York, Type: System.String
Key: 1, Value: Number One, Type: System.String

--- 单独遍历所有键 ---
Key: name
Key: city
Key: 1

--- 单独遍历所有值 ---
Value: Alice
Value: New York
Value: Number One

5. Hashtable 的缺点和现代替代品:Dictionary<TKey, TValue>

由于 Hashtable 是非泛型的,它存在一些显著的缺点:

  1. 性能开销(装箱/拆箱) :当存储值类型(如 int, struct)时,会发生装箱 操作(将值类型转换为 object),在读取时会发生拆箱 (将 object 转换回值类型),这会影响性能。
  2. 类型不安全 :编译器无法检查类型,容易在运行时因类型转换错误而引发 InvalidCastException
  3. 代码可读性差:需要频繁地进行显式类型转换。
推荐使用的现代替代品:Dictionary<TKey, TValue>

Dictionary<TKey, TValue> 位于 System.Collections.Generic 命名空间中,是 Hashtable泛型版本,解决了上述所有问题。

代码对比:

csharp 复制代码
using System.Collections.Generic;

// 使用 Hashtable (老方法,不推荐)
Hashtable oldStyle = new Hashtable();
oldStyle.Add("age", 25);
int ageOld = (int)oldStyle["age"]; // 需要拆箱,可能 InvalidCastException

// 使用 Dictionary (现代方法,推荐)
Dictionary<string, int> modernDict = new Dictionary<string, int>();
modernDict.Add("age", 25);
int ageModern = modernDict["age"]; // 无需转换,类型安全,性能更好

Dictionary<TKey, TValue> 的优势:

  • 类型安全:在编译时即可检查类型。
  • 性能更好:避免了装箱和拆箱。
  • 代码更清晰:无需显式类型转换。

6. 总结

特性 Hashtable Dictionary<TKey, TValue>
命名空间 System.Collections System.Collections.Generic
类型 非泛型 泛型
性能 较低(存在装箱/拆箱)
类型安全 否(运行时检查) 是(编译时检查)
键/值是否可为null 键不能为null,值可以为null 取决于泛型类型参数(例如 string 可为null,int 不能)
推荐使用 遗留代码或需要与 .NET 1.x 兼容时 所有新项目

结论:

虽然理解 Hashtable 的工作原理对于学习数据结构和哈希概念非常重要,但在实际的 C# 开发中,你应该优先使用 Dictionary<TKey, TValue>Hashtable 主要用于维护旧的代码库。

相关推荐
习习.y23 分钟前
关于python中的面向对象
开发语言·python
lingggggaaaa23 分钟前
免杀对抗——C2远控篇&PowerShell&有无文件落地&C#参数调用&绕AMSI&ETW&去混淆特征
c语言·开发语言·笔记·学习·安全·microsoft·c#
技术净胜23 分钟前
MATLAB 基因表达数据处理与可视化全流程案例
开发语言·matlab
友友马24 分钟前
『Qt』多元素控件
开发语言·qt
hmbbcsm31 分钟前
练习python题目小记(六)
开发语言·python
4***V2021 小时前
Vue3响应式原理详解
开发语言·javascript·ecmascript
q***98521 小时前
VS Code 中如何运行Java SpringBoot的项目
java·开发语言·spring boot
共享家95271 小时前
QT-界面优化(中)
开发语言·qt
咩图1 小时前
WPF+Prism8.0.0.1909+C#创建一个桌面程序
c#·wpf·prism