第三章:通用数据结构 III——映射结构(规模下的空间与过滤能力)
绪论:从搜索结构到计算结构
- 第二章解决的问题是:如何减少查找过程中需要访问的数据数量。
为此,系统在存储结构之上建立 索引结构,通过组织查找路径逐步压缩搜索空间。
常见方式包括:
- 有序索引
- 分层索引
- 分块索引
通过这些结构,查找复杂度可以从 O(n) 降低到 O(log n)。
但当数据规模继续扩大时,这种方法仍然存在新的限制:
- 查找过程仍然需要 沿索引路径逐层比较
- 随着数据规模扩大,这种路径搜索仍然会产生明显的访问成本
因此系统开始探索另一种思路:
是否可以通过计算直接确定数据位置,而不再依赖搜索路径?
如果数据位置可以通过函数计算得到,那么查找过程将不再需要逐层搜索。
这种通过 计算定位数据位置 的结构,被称为:
映射结构
定义
- 通过函数或规则,将 Key 直接映射到数据位置或状态,从而减少或避免搜索过程。
核心思想
- 通过计算替代搜索路径。
主要能力
- 在大规模系统中,映射结构通常通过以下方式降低访问成本:
- 直接映射:通过函数计算直接定位数据(如哈希表)
- 空间压缩:通过位级结构降低存储成本(如位图)
- 概率过滤:通过概率模型快速判断存在性(如布隆过滤器)
- 规模估计:通过统计方法估算数据规模(如 HyperLogLog)
一、确定性映射:哈希表
定义
- 一种通过 哈希函数将 Key 映射到固定地址范围 的数据结构。
核心思想
- 通过函数计算直接确定数据位置,从而避免搜索过程。
结构
- 底层通常为一个 数组
- 每个数组槽位称为 桶(Bucket)
- 每个 Key 通过哈希函数映射到一个桶
机制
-
常见映射规则:
Index = Hash(Key) mod NHash(Key)为哈希函数N为数组容量
-
查找流程
- 计算 Key 的哈希值
- 定位到对应数组下标
- 若发生冲突,则在桶中继续查找
-
插入流程
- 计算 Key 的哈希值
- 定位到桶
- 若发生冲突,则按照冲突处理策略存储元素
-
扩容机制
-
当 负载因子 过高时需要扩容
-
扩容过程包括:
- 分配更大的数组
- 重新计算所有元素位置(重新哈希)
-
扩容复杂度为:
O(n)
-
哈希冲突
-
原因
- 哈希函数需要将 无限 Key 空间映射到有限数组空间
- 根据 抽屉原理,不同 Key 必然可能映射到同一位置
常见冲突解决策略
-
拉链法(分离链接)
-
结构:
- 每个桶维护一个链表
- 冲突元素存储在链表中
-
工程优化:当链表长度超过阈值时,可转换为 红黑树
-
特点
- 插入稳定,实现简单
- 节点离散存储,缓存局部性较差
-
工程实践:Java
HashMap
-
-
开放寻址
-
结构:
- 所有元素必须存储在数组中
- 冲突时按规则寻找下一个空位置
-
常见策略:
- 线性探测
- 二次探测
- 双重哈希
-
特点:
- 数据物理连续,缓存友好
- 容易产生 聚簇效应
-
工程实践:Google
flat_hash_map
-
布谷鸟哈希
-
结构:
- 每个元素拥有 K 个候选位置
- 由不同哈希函数计算原始Key得到
-
插入流程:新元素占位→原元素被踢出→原元素寻找新的候选位置→循环直到稳定或扩容
-
特点:
- 查询复杂度稳定接近
O(1) - 空间利用率高
k=3时空间利用率可达到 90% 以上
- 查询复杂度稳定接近
-
工程实践:高性能哈希表实现。
-
-
特征
- 查找、插入复杂度:
O(1) - 扩容复杂度:
O(n) - 随机访问能力强
- 不支持范围查询
总结
- 哈希表通过:
Key → Hash → Array Index,实现 常数时间的数据定位能力。 - 在工程实践中,常用于:数据容器、缓存索引、高速查找结构。
二、位级压缩映射:位图
定义
- 一种 使用 1 bit 表示一个状态 的数据结构。
结构
- 底层为一个连续二进制数组
- 每一位表示一个状态
机制
- 核心映射关系:
Integer → Bit Position - 定位方式:
Index / 8定位到字节,Index % 8定位到具体的位。 - 常见操作:设置状态、清除状态、判断状态
- 集合运算:AND、OR、XOR
特征
- 空间占用极小
- 支持高速位运算
- 只能表示有限范围整数集合
- 不适用于稀疏数据
总结
- 位图通过:
整数 → 位位置,实现 极高密度的数据压缩存储。 - 在工程实践中,常用于:用户签到、在线状态、大规模布尔标记、集合交并统计
三、概率映射:布隆过滤器
定义
- 一种 概率型集合结构,用于判断元素 是否可能存在。
结构
-
布隆过滤器由两部分组成:
- 一个 位数组
- K 个哈希函数
-
每个元素会被映射到多个 bit 位置。
机制
-
插入:
Key → k个哈希函数→ 对应bit置1 -
查询
- 若任意 bit 为
0→ 一定不存在 - 若全部为
1→ 可能存在
- 若任意 bit 为
特征
- 空间占用极小
- 查询速度极快
- 存在 误判率
- 标准版本 无法删除元素
总结
- 布隆过滤器通过:
Key → 多哈希函数 → 位数组,实现 极低空间成本下的存在性判断。 - 在工程实践中,常用于:数据库查询过滤、缓存穿透防护、存储系统预判。
- 因此常被称为:存储系统的“门神” ,在访问昂贵资源前进行快速过滤。
四、统计映射:HyperLogLog
定义
- 一种用于 集合基数估计(Cardinality Estimation) 的概率统计结构。
- 用于在极小空间下估计:集合中不同元素的数量。
核心原理
- 利用 哈希值前导零长度 估算基数
结构
-
HyperLogLog通常由两部分组成:
- 多个 计数桶
- 一个 哈希函数
机制
-
基本过程:
- 对元素进行哈希
- 观察哈希值前导零长度
- 记录统计结果
- 通过概率模型估算集合规模
特征
- 空间极小(KB级)
- 支持海量数据统计
- 结果为 近似值
总结
- HyperLogLog通过:
哈希 → 前导零统计 → 基数估计,实现 大规模集合统计能力。 - 在工程实践中,常用于:UV 统计、去重估计、大规模数据分析。
- 通常会采用 分桶统计 以减少极端情况带来的误差。
五、映射稳定性:一致性哈希
定义
- 一种用于保持 哈希映射稳定性 的算法
- 主要用于 分布式系统中的数据分配
结构
- 一致性哈希将哈希空间抽象为
0 ~ 2^32,形成一个 虚拟环(Hash Ring) 。 - 系统节点与数据 Key 都被映射到这个环上。
机制
-
映射规则:
- 节点映射到环上
- Key 顺时针寻找最近节点
-
顺时针规则保证:每个 Key 只属于一个连续区间
-
当节点加入或离开时:只有相邻区间的数据需要重新分配
特征
- 映射关系稳定
- 扩容只影响局部数据
总结
- 一致性哈希通过:
Key → Hash Ring → Node,实现 稳定的数据映射关系。 - 在工程实践中,通常引入 虚拟节点(Virtual Nodes) ,用于提升负载均衡能力
本章总结
- 第一章解决的问题是:数据如何存放
- 第二章解决的问题是:如何通过结构组织压缩搜索空间
- 本章解决的问题是:如何通过计算降低大规模数据的访问成本
为此,系统引入了一类新的结构:映射结构。
其核心思想是:
- 通过计算替代搜索
这些结构通过函数计算、空间压缩或概率模型,使系统能够在有限资源下处理海量数据。
常见方式包括:
- 确定性映射:通过函数计算直接定位数据(如哈希表)
- 位级压缩:通过位级结构降低存储成本(如位图)
- 概率过滤:通过概率模型快速判断存在性(如布隆过滤器)
- 规模估计:通过统计方法估算集合规模(如 HyperLogLog)
- 稳定映射:在分布式环境中保持映射关系稳定(如一致性哈希)
这些结构的共同特点是:
通过计算、压缩或概率模型减少数据访问成本。
下一问题
前三章讨论的是 通用数据组织能力:
- 存储结构
- 索引结构
- 映射结构
这些结构提供了通用的数据组织方式,但在真实系统中,数据结构往往还会受到 具体场景约束 的影响。
当约束发生变化时,结构设计的目标也会随之改变,通用结构往往无法达到最佳性能。
因此,在许多系统中会出现 针对特定约束优化的数据结构。
当问题的约束条件发生变化时,数据结构应该如何调整其组织方式?
接下来的章节将讨论这些 场景驱动的结构设计。