.NET 数据结构

.NET 自带数据结构底层实现详解

一、线性数据结构

1.1 List - 动态数组

底层实现: 可变大小的数组

核心字段:

internal T[] _items;        // 存储元素的数组
internal int _size;         // 当前元素数量
internal int _version;      // 版本号,用于检测集合修改
private const int DefaultCapacity = 4;  // 默认初始容量

关键特性:

  • 存储方式: 连续内存的动态数组
  • 扩容策略: 容量不足时,新容量 = Math.Max(当前容量 * 2, 当前容量 + 4)
  • 时间复杂度:
    • 索引访问:O(1)
    • 末尾添加:平摊O(1),最坏O(n)
    • 中间插入/删除:O(n)
  • 空间效率: 高,元素紧密排列

1.2 Queue - 队列

底层实现: 循环数组(环形缓冲区)

核心字段:

private T[] _array;         // 存储元素的数组
private int _head;          // 队头索引
private int _tail;          // 队尾索引  
private int _size;          // 元素数量
private int _version;       // 版本号

关键特性:

  • 存储方式: 循环数组,避免元素移动
  • 操作机制:
    • Enqueue:在_tail位置添加,_tail = (_tail + 1) % _array.Length
    • Dequeue:从_head位置取出,_head = (_head + 1) % _array.Length
  • 扩容策略: 容量不足时创建新数组,重新排列元素
  • 时间复杂度: Enqueue和Dequeue均为O(1)

1.3 Stack - 栈

底层实现: 动态数组

核心字段:

private T[] _array;         // 存储元素的数组
private int _size;          // 元素数量(同时也是栈顶指针)
private int _version;       // 版本号
private const int DefaultCapacity = 4;

关键特性:

  • 存储方式: 数组,_size指向下一个可用位置
  • 操作机制:
    • Push:_array[_size++] = item
    • Pop:return _array[--_size]
  • 扩容策略: 与List类似,容量翻倍
  • 时间复杂度: Push和Pop均为O(1)

二、链表结构

2.1 LinkedList - 双向循环链表

底层实现: 双向循环链表

核心字段:

internal LinkedListNode<T>? head;  // 头节点
internal int count;                // 节点数量
internal int version;              // 版本号

节点结构:

public sealed class LinkedListNode<T>
{
    internal LinkedList<T>? list;     // 所属链表
    internal LinkedListNode<T>? next; // 下一个节点
    internal LinkedListNode<T>? prev; // 前一个节点
    internal T item;                  // 存储的值
}

关键特性:

  • 存储方式: 每个节点包含prev/next指针,形成双向循环链表
  • 循环结构: head.prev指向尾节点,尾节点.next指向head
  • 时间复杂度:
    • 头尾插入/删除:O(1)
    • 中间插入/删除(已知节点):O(1)
    • 查找:O(n)

三、哈希表结构

3.1 Dictionary<TKey, TValue> - 哈希表

底层实现: 数组 + 链地址法解决冲突

核心字段:

private int[]? _buckets;              // 哈希桶数组
private Entry[]? _entries;            // 存储键值对的数组
private int _count;                   // 元素数量
private int _freeList;                // 空闲链表头
private IEqualityComparer<TKey>? _comparer; // 键比较器

Entry结构:

private struct Entry
{
    public uint hashCode;   // 哈希值
    public int next;        // 下一个节点索引(链表结构)
    public TKey key;        // 键
    public TValue value;    // 值
}

关键特性:

  • 冲突解决: 链地址法,相同哈希位置的元素通过链表连接
  • 扩容机制: 负载因子达到1.0时扩容,新容量为大于当前容量2倍的最小质数
  • 时间复杂度: 平均O(1),最坏O(n)

3.2 HashSet - 哈希集合

底层实现: 与Dictionary<TKey, TValue>相同的哈希表结构

核心特性:

  • 复用Dictionary实现: 只存储键,值部分忽略
  • 去重机制: 基于哈希值和Equals比较
  • 时间复杂度: 与Dictionary相同

3.3 ConcurrentDictionary<TKey, TValue> - 并发哈希表

底层实现: 分段锁 + 哈希表

核心结构:

private volatile Tables _tables;  // 内部表结构

private sealed class Tables
{
    internal readonly VolatileNode[] _buckets;      // 哈希桶
    internal readonly object[] _locks;              // 分段锁数组
    internal readonly int[] _countPerLock;          // 每段的元素计数
    internal readonly IEqualityComparer<TKey>? _comparer;
}

关键特性:

  • 并发策略: 分段锁,将哈希表分为多个段,每段独立加锁
  • 锁粒度: 默认锁数量等于CPU核心数,最大1024个锁
  • 读操作: 大部分情况下无锁(volatile读取)
  • 写操作: 只锁定对应段,提高并发性能

四、树型结构

4.1 SortedSet - 红黑树

底层实现: 红黑树(自平衡二叉搜索树)

核心字段:

private Node? root;           // 根节点
private IComparer<T> comparer; // 比较器
private int count;            // 节点数量

节点结构:

internal sealed class Node
{
    internal T Item;              // 存储的值
    internal Node? Left;          // 左子节点
    internal Node? Right;         // 右子节点
    internal NodeColor Color;     // 节点颜色(红/黑)
    internal bool IsRed => Color == NodeColor.Red;
}

关键特性:

  • 平衡策略: 红黑树性质保证树的平衡
  • 有序性: 中序遍历得到有序序列
  • 时间复杂度: 插入、删除、查找均为O(log n)

4.2 SortedDictionary<TKey, TValue> - 基于红黑树的字典

底层实现: 内部使用TreeSet<KeyValuePair<TKey, TValue>>

核心字段:

private readonly TreeSet<KeyValuePair<TKey, TValue>> _set;

关键特性:

  • 复用SortedSet: 将键值对作为整体存储在红黑树中
  • 排序依据: 基于键的比较器排序
  • 时间复杂度: 与SortedSet相同,O(log n)

五、特殊数据结构

5.1 StringBuilder - 可变字符串

底层实现: 链式的字符数组块

核心字段:

internal char[] m_ChunkChars;          // 当前块的字符数组
internal StringBuilder? m_ChunkPrevious; // 前一个块
internal int m_ChunkLength;            // 当前块的字符数
internal int m_ChunkOffset;            // 当前块在整个字符串中的偏移

关键特性:

  • 分块存储: 每个块默认16个字符,避免频繁数组复制
  • 链式结构: 多个块通过指针连接
  • 扩容策略: 块满时创建新块,而非整体扩容

5.2 Array - 固定大小数组

底层实现: 连续内存块

关键特性:

  • 内存布局: 元素在内存中连续存储
  • 类型安全: 编译时类型检查
  • 多维支持: 支持多维数组和交错数组
  • 性能: 最佳的缓存局部性和访问性能

六、性能对比总结

数据结构 查找 插入 删除 空间复杂度 主要特点
List O(1)索引
O(n)值
O(1)末尾
O(n)中间
O(n) O(n) 随机访问,缓存友好
LinkedList O(n) O(1)已知位置 O(1)已知位置 O(n) 插入删除高效
Dictionary<TKey,TValue> O(1)平均 O(1)平均 O(1)平均 O(n) 快速键值查找
HashSet O(1)平均 O(1)平均 O(1)平均 O(n) 去重,集合运算
SortedSet O(log n) O(log n) O(log n) O(n) 自动排序,范围查询
SortedDictionary<TKey,TValue> O(log n) O(log n) O(log n) O(n) 有序键值对
Queue - O(1) O(1) O(n) FIFO
Stack - O(1) O(1) O(n) LIFO
ConcurrentDictionary<TKey,TValue> O(1)平均 O(1)平均 O(1)平均 O(n) 线程安全

七、选择建议

  1. 频繁随机访问:选择Array或List
  2. 频繁插入删除(中间位置):选择LinkedList
  3. 键值查找:选择Dictionary<TKey,TValue>
  4. 去重和集合运算:选择HashSet
  5. 需要排序:选择SortedSet或SortedDictionary<TKey,TValue>
  6. 队列操作:选择Queue
  7. 栈操作:选择Stack
  8. 多线程环境:选择ConcurrentDictionary<TKey,TValue>或其他Concurrent集合
  9. 字符串拼接:选择StringBuilder

这些数据结构的设计充分考虑了性能、内存使用和特定场景的需求,是.NET框架高性能的重要基础。

posted @ 2025-08-18 14:53  MadLongTom  阅读(14)  评论(0)    收藏  举报