哈希表(Hash Table)是计算机科学中最重要的数据结构之一,也是算法面试中的高频考点。它通过建立键(key)与值(value)之间的映射关系,实现了平均时间复杂度为O(1)的快速查找。在实际开发中,哈希表被广泛应用于缓存系统、数据库索引、编译器符号表等场景。
哈希表的核心在于哈希函数的设计。一个好的哈希函数需要满足两个基本条件:一是计算速度快,二是能够将键均匀分布到整个地址空间。以Java中的HashMap为例,它使用对象的hashCode()方法获取初始哈希值,再通过扰动函数减少碰撞概率:
java复制static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
注意:哈希冲突是不可避免的,常见的解决方法有开放寻址法和链地址法。Java的HashMap采用数组+链表/红黑树的组合方式,当链表长度超过8时转换为红黑树,以优化极端情况下的查询性能。
这是哈希表最经典的入门题目。给定一个整数数组nums和一个目标值target,需要在数组中找出和为目标值的两个整数。暴力解法需要O(n²)的时间复杂度,而使用哈希表可以将时间复杂度降至O(n):
python复制def twoSum(nums, target):
hashmap = {}
for i, num in enumerate(nums):
complement = target - num
if complement in hashmap:
return [hashmap[complement], i]
hashmap[num] = i
return []
关键点在于:遍历时先查询哈希表中是否存在当前数的补数(target-num),如果存在立即返回结果,否则将当前数存入哈希表。这种"边查边存"的策略确保了每个元素只需要被处理一次。
这道题要求将字母异位词(由相同字母重新排列形成的单词)分组。哈希表的键设计是解题关键——可以使用排序后的字符串作为键:
python复制def groupAnagrams(strs):
from collections import defaultdict
ans = defaultdict(list)
for s in strs:
key = ''.join(sorted(s))
ans[key].append(s)
return list(ans.values())
对于N个字符串,每个字符串平均长度K,时间复杂度为O(NKlogK)。如果字符串包含Unicode字符,直接排序可能有问题,此时可以用字符计数数组作为键:
python复制key = tuple(sorted(Counter(s).items()))
LRU(Least Recently Used)缓存淘汰算法是哈希表+双向链表的经典组合。Python中可以使用OrderedDict简化实现:
python复制from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache: return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
手动实现双向链表版本虽然复杂但性能更优,适合生产环境。每个节点需要维护prev/next指针,哈希表存储key到节点的映射,链表头部是最久未使用的元素,尾部是最近使用的元素。
当需要判断元素是否存在于海量数据集合中,且允许一定误判率时,布隆过滤器(Bloom Filter)是比哈希表更节省空间的选择。它使用k个哈希函数和m位的位数组:
python复制import mmh3 # MurmurHash3非加密哈希函数
from bitarray import bitarray
class BloomFilter:
def __init__(self, size, hash_num):
self.size = size
self.hash_num = hash_num
self.bit_array = bitarray(size)
self.bit_array.setall(0)
def add(self, string):
for seed in range(self.hash_num):
result = mmh3.hash(string, seed) % self.size
self.bit_array[result] = 1
def lookup(self, string):
for seed in range(self.hash_num):
result = mmh3.hash(string, seed) % self.size
if self.bit_array[result] == 0:
return False
return True
布隆过滤器广泛应用于网络爬虫URL去重、Redis缓存穿透防护、垃圾邮件过滤等场景。误判率p的计算公式为:
p ≈ (1 - e^(-kn/m))^k
其中n是已插入元素数量。根据这个公式可以推导出最优的k值:
k = (m/n)ln2 ≈ 0.7*(m/n)
当恶意攻击者精心构造大量哈希值相同的key时,会导致哈希表退化为链表,查询时间复杂度从O(1)恶化到O(n)。这在Web服务中可能引发DoS攻击。防护措施包括:
哈希表的性能与负载因子(load factor,元素数量/桶数量)密切相关。当负载因子超过阈值(通常0.75)时,需要动态扩容:
Python字典的扩容策略值得参考:当字典可用空间不足2/3时触发扩容,新大小取第一个大于等于used*4的2的幂次方。扩容时采用渐进式rehash,避免一次性操作导致卡顿。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 查询性能突然下降 | 哈希碰撞严重 | 检查键的哈希分布,考虑更换哈希函数 |
| 内存占用过高 | 负载因子过低 | 调整初始容量,减少扩容次数 |
| 并发修改异常 | 多线程竞争 | 改用ConcurrentHashMap或加锁 |
| 迭代顺序不稳定 | 哈希表无序特性 | 改用LinkedHashMap或额外维护顺序列表 |
Redis的哈希表实现堪称典范,它采用渐进式rehash策略平衡性能与内存:
这种设计使得rehash过程对性能影响极小,值得在需要高性能哈希表的场景中借鉴。
在分布式系统中,一致性哈希算法可以解决节点增减导致的大量数据迁移问题。基本实现步骤:
优化版本会引入虚拟节点,使负载分布更均匀。Python示例:
python复制import hashlib
from bisect import bisect
class ConsistentHash:
def __init__(self, nodes=None, replicas=3):
self.replicas = replicas
self.ring = []
self.nodes = set()
if nodes:
for node in nodes:
self.add_node(node)
def _hash(self, key):
return int(hashlib.md5(key.encode()).hexdigest(), 16)
def add_node(self, node):
for i in range(self.replicas):
virtual_node = f"{node}#{i}"
hash_val = self._hash(virtual_node)
bisect.insort(self.ring, (hash_val, node))
self.nodes.add(node)
def get_node(self, key):
if not self.ring: return None
hash_val = self._hash(key)
idx = bisect.bisect(self.ring, (hash_val, None)) % len(self.ring)
return self.ring[idx][1]
这种算法广泛应用于分布式缓存、负载均衡等场景,如Nginx的upstream配置中就支持一致性哈希策略。