第114集:符号表(三)
核心知识点讲解
哈希表实现
哈希表是实现符号表的高效数据结构,它通过哈希函数将符号名映射到表中的位置,实现快速查找。
哈希表的关键组件:
哈希函数:将符号名转换为哈希值
- 好的哈希函数应具有均匀分布性
- 常见的哈希函数:除留余数法、乘法哈希、斐波那契哈希
冲突处理:
- 链地址法:每个哈希桶存储一个链表
- 开放地址法:通过探测寻找下一个空闲位置
- 再哈希法:使用多个哈希函数
负载因子:
- 哈希表中元素数量与表大小的比值
- 通常设置为0.7-0.8,超过阈值时需要扩容
哈希表符号表实现:
class HashSymbolTable:
def __init__(self, initial_size=128, load_factor=0.75):
self.size = initial_size
self.load_factor = load_factor
self.count = 0
self.buckets = [[] for _ in range(self.size)]
def _hash(self, name):
"""简单的哈希函数"""
hash_value = 0
for char in name:
hash_value = (hash_value * 31 + ord(char)) % self.size
return hash_value
def _resize(self):
"""扩容哈希表"""
new_size = self.size * 2
new_buckets = [[] for _ in range(new_size)]
# 重新哈希所有元素
for bucket in self.buckets:
for symbol in bucket:
new_hash = 0
for char in symbol.name:
new_hash = (new_hash * 31 + ord(char)) % new_size
new_buckets[new_hash].append(symbol)
self.size = new_size
self.buckets = new_buckets
def insert(self, symbol):
"""插入符号"""
# 检查负载因子
if self.count / self.size >= self.load_factor:
self._resize()
hash_value = self._hash(symbol.name)
bucket = self.buckets[hash_value]
# 检查是否已存在
for existing_symbol in bucket:
if existing_symbol.name == symbol.name:
return False
bucket.append(symbol)
self.count += 1
return True
def lookup(self, name):
"""查找符号"""
hash_value = self._hash(name)
bucket = self.buckets[hash_value]
for symbol in bucket:
if symbol.name == name:
return symbol
return None
def remove(self, name):
"""删除符号"""
hash_value = self._hash(name)
bucket = self.buckets[hash_value]
for i, symbol in enumerate(bucket):
if symbol.name == name:
bucket.pop(i)
self.count -= 1
return True
return False平衡树实现
平衡树(如AVL树、红黑树)是另一种高效的符号表实现方式,它保证了O(log n)的查找、插入和删除时间复杂度。
平衡树的优点:
- 有序性:符号按名称排序存储
- 稳定的时间复杂度:不受哈希函数质量影响
- 支持范围查询:如查找所有以某个前缀开头的符号
- 内存使用更可预测:没有哈希冲突带来的内存开销
AVL树符号表实现:
class AVLNode:
def __init__(self, symbol):
self.symbol = symbol
self.left = None
self.right = None
self.height = 1
class AVLSymbolTable:
def __init__(self):
self.root = None
def _height(self, node):
if not node:
return 0
return node.height
def _balance_factor(self, node):
if not node:
return 0
return self._height(node.left) - self._height(node.right)
def _rotate_right(self, y):
x = y.left
T2 = x.right
x.right = y
y.left = T2
y.height = max(self._height(y.left), self._height(y.right)) + 1
x.height = max(self._height(x.left), self._height(x.right)) + 1
return x
def _rotate_left(self, x):
y = x.right
T2 = y.left
y.left = x
x.right = T2
x.height = max(self._height(x.left), self._height(x.right)) + 1
y.height = max(self._height(y.left), self._height(y.right)) + 1
return y
def _insert(self, node, symbol):
if not node:
return AVLNode(symbol)
if symbol.name < node.symbol.name:
node.left = self._insert(node.left, symbol)
elif symbol.name > node.symbol.name:
node.right = self._insert(node.right, symbol)
else:
return node # 重复键
node.height = max(self._height(node.left), self._height(node.right)) + 1
balance = self._balance_factor(node)
# 左左情况
if balance > 1 and symbol.name < node.left.symbol.name:
return self._rotate_right(node)
# 右右情况
if balance < -1 and symbol.name > node.right.symbol.name:
return self._rotate_left(node)
# 左右情况
if balance > 1 and symbol.name > node.left.symbol.name:
node.left = self._rotate_left(node.left)
return self._rotate_right(node)
# 右左情况
if balance < -1 and symbol.name < node.right.symbol.name:
node.right = self._rotate_right(node.right)
return self._rotate_left(node)
return node
def insert(self, symbol):
self.root = self._insert(self.root, symbol)
def _search(self, node, name):
if not node:
return None
if name < node.symbol.name:
return self._search(node.left, name)
elif name > node.symbol.name:
return self._search(node.right, name)
else:
return node.symbol
def lookup(self, name):
return self._search(self.root, name)性能对比
| 数据结构 | 查找时间 | 插入时间 | 删除时间 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 线性表 | O(n) | O(n) | O(n) | 实现简单 | 效率低 |
| 哈希表 | 平均O(1) | 平均O(1) | 平均O(1) | 速度快 | 无序,需要处理冲突 |
| 二叉搜索树 | O(log n) | O(log n) | O(log n) | 有序 | 可能不平衡 |
| AVL树 | O(log n) | O(log n) | O(log n) | 有序,平衡 | 实现复杂 |
| 红黑树 | O(log n) | O(log n) | O(log n) | 有序,平衡,插入删除更快 | 实现复杂 |
优化技巧
缓存热点符号:
- 为频繁访问的符号建立缓存
- 减少查找时间
字符串池:
- 对符号名进行 intern 处理
- 减少内存使用,加速字符串比较
批量操作:
- 支持批量插入和删除
- 减少树的旋转或哈希表的扩容次数
惰性删除:
- 标记删除而非立即删除
- 延迟实际删除操作到合适时机
预分配空间:
- 根据预期的符号数量预分配合适的大小
- 减少运行时扩容开销
实用案例分析
实际编译器中的符号表选择
GCC:
- 使用哈希表作为主要实现
- 结合链表处理冲突
- 针对C/C++的作用域规则进行优化
Clang/LLVM:
- 使用哈希表实现
- 采用更复杂的作用域管理
- 支持增量编译
Java编译器:
- 使用哈希表和平衡树结合的方式
- 支持复杂的类型系统
- 需要处理泛型和继承关系
符号表性能优化实例
字符串池的使用:
class StringPool:
def __init__(self):
self.strings = {}
def intern(self, string):
if string not in self.strings:
self.strings[string] = string
return self.strings[string]
# 使用字符串池减少内存使用和加速比较
string_pool = StringPool()
symbol_name = string_pool.intern("counter")缓存热点符号:
class CachedSymbolTable:
def __init__(self, capacity=16):
self.symbol_table = HashSymbolTable()
self.cache = {}
self.cache_capacity = capacity
def lookup(self, name):
# 先查缓存
if name in self.cache:
return self.cache[name]
# 缓存未命中,查符号表
symbol = self.symbol_table.lookup(name)
# 更新缓存
if symbol:
if len(self.cache) >= self.cache_capacity:
# 简单的LRU:删除第一个元素
self.cache.pop(next(iter(self.cache)))
self.cache[name] = symbol
return symbol小结
符号表的实现技术对编译器性能有着重要影响:
- 哈希表:适用于对查找速度要求高的场景
- 平衡树:适用于需要有序性和范围查询的场景
- 性能优化:通过缓存、字符串池等技术进一步提升性能
- 实际选择:根据编译器的具体需求和目标语言的特性选择合适的实现
在实际编译器开发中,符号表的设计需要在实现复杂度和性能之间取得平衡,同时考虑到目标语言的作用域规则和类型系统的复杂性。