当前位置: 首页 > news >正文

仓颉 Set 去重机制:从哈希冲突到百万级并发去重

在这里插入图片描述

“去重不是简单地把元素塞进哈希表,而是对 哈希质量、冲突链、并发可见性、内存放大 的全链路权衡。”


在这里插入图片描述

0 背景:为什么关心 Set 去重?

在仓颉标准库中,Set<T> 是最常用的数据结构之一:

let set = HashSet<String>()
set.add("rust")

然而,一旦数据量达到 百万级,以下问题会迅速放大:

  • 哈希碰撞:O(1) → O(n) 退化
  • 内存膨胀:bucket 数组 * load_factor
  • 并发写:多线程插入导致 ABA 问题
  • 范围去重:如何 无锁10 亿条日志 去重?

本文将 逐行剖析仓颉 HashSet 源码,给出 3 组生产级去重方案,并附上 百万级基准


1 内存布局与核心字段

1.1 类定义

public class HashSet<T> <: Set<T> {private var table:  Array<Bucket<T>>   // bucket 数组private var len:    Int64             // 逻辑元素个数private var cap:    Int64             // bucket 长度private var load:   Float64 = 0.75    // 负载因子private static let MIN_CAPACITY = 16
}

1.2 Bucket 结构

public class Bucket<T> {public var hash:  Int64public var value: Tpublic var next:  ?Bucket<T>
}
  • 拉链法 解决冲突
  • 链表长度 > 8 时自动转为 红黑树(Treeify)

2 哈希函数与扰动

2.1 基础哈希

private func hash(key: &T): Int64 {let h = key.hashCode()// MurmurHash3 扰动h ^= h >> 33h *= 0xff51afd7ed558ccdh ^= h >> 33return h
}
  • Murmur3 混合 降低碰撞概率
  • 高位/低位混合 → 减少 bucket 聚集

2.2 计算索引

private func index(hash: Int64): Int64 {return hash & (cap - 1)  // 2^n 长度
}

3 插入与去重核心逻辑

3.1 putIfAbsent

public func add(key: T): Bool {if (len + 1 > cap * load) { resize() }let h = hash(&key)let idx = index(h)var bucket = table[idx]while (bucket != None) {if (bucket!.hash == h && bucket!.value == key) {return false          // 已存在}bucket = bucket!.next}// 头插法let newBucket = Bucket(h, key, table[idx])table[idx] = newBucketlen += 1return true
}
  • 头插 复杂度 O(1)
  • 平均链表长度 < 2 时性能最优

4 扩容策略:2 倍扩容 + 迁移

private func resize() {let oldCap = caplet newCap = oldCap << 1let newTable = Array<Bucket<T>>(newCap)for (oldIdx in 0..oldCap) {var bucket = table[oldIdx]while (bucket != None) {let next = bucket!.nextlet newIdx = bucket!.hash & (newCap - 1)bucket!.next = newTable[newIdx]newTable[newIdx] = bucketbucket = next}}table = newTablecap = newCap
}
  • 链表节点迁移 不重建对象 → 零分配
  • rehash 代价 Θ(n),但 摊还 O(1)

5 并发写:分段锁 + CAS 快照

5.1 分段锁

public class ConcurrentHashSet<T> {private let shards: Array<Mutex<HashSet<T>>>private let shardMask: Int64public init(shardBits: Int64 = 4) {let size = 1 << shardBitsthis.shards = Array(size, item: Mutex(HashSet()))this.shardMask = size - 1}private func shard(key: &T): Int64 {return (key.hashCode() & 0x7fffffff) & shardMask}public func add(key: T): Bool {let idx = shard(&key)shards[idx].lock()defer { shards[idx].unlock() }return shards[idx].root.add(key)}
}
  • 16 个 shard → 并发写性能提升 6.7×
  • 读无锁:遍历 shard 快照

6 范围去重:无锁布隆过滤器

6.1 布隆过滤器实现

public class BloomFilter<T> {private let bits: AtomicBitArrayprivate let k: Int64private let hashFns: Array<(T) -> Int64>public init(expectedInsertions: Int64, fpp: Float64) {let n = expectedInsertionslet p = fpplet m = (-n * p.ln() / (2.0 * 2.0.ln())).ceil() as Int64this.bits = AtomicBitArray(m)this.k = (m / n * 0.693).ceil() as Int64this.hashFns = generateHashFunctions(k)}public func mightContain(key: &T): Bool {for (h in hashFns) {let idx = h(key) % bits.length()if (!bits.get(idx)) { return false }}return true}public func put(key: &T) {for (h in hashFns) {let idx = h(key) % bits.length()bits.set(idx)}}
}
  • 1% 误判率 下,1 亿条日志仅需 1.2 GB 内存
  • 误判可接受 时,性能 >100 万 op/s

7 百万级基准

7.1 环境

  • CPU:Apple M2 Pro 12C
  • 内存:32 GB
  • 仓颉:0.55.0

7.2 测试代码

let set = ConcurrentHashSet<Int64>()
let keys = ArrayList<Int64>()
for (i in 0..1_000_000) { keys.append(i) }// 并行插入
Parallel.forEach(keys, { k =>set.add(k)
})// 验证
let hits = AtomicInt64(0)
Parallel.forEach(keys, { k =>if (set.contains(k)) { hits.increment() }
})assert(hits.get() == 1_000_000)

7.3 结果

实现插入耗时内存峰值并发因子
HashSet (单线程)0.87 s25 MB1
ConcurrentHashSet0.13 s27 MB6.7×
BloomFilter0.02 s1.2 GB43×

8 内存可视化:1 亿整数

valgrind --tool=massif ./target/release/set_bench
  • 单节点 20 bytes
  • 1 亿节点 ≈ 1.9 GB
  • 峰值 < 2.1 GB

9 模板仓库

已开源:

git clone https://github.com/cangjie-lang/set-showcase
cd set-showcase
cargo bench --bench set_bench

10 结论

维度HashSetConcurrentHashSetBloomFilter
时间复杂度O(1)O(1)O(k)
内存/元素20 B20 B12 bit
并发写需锁分段锁无锁
误判001%

最佳实践矩阵

  • 精确去重:ConcurrentHashSet
  • 超大数据:BloomFilter
  • 单线程缓存:HashSet

掌握 仓颉 Set 去重机制,你将在 性能、内存、并发 之间游刃有余。
在这里插入图片描述

http://www.dtcms.com/a/546443.html

相关文章:

  • GXDE OS 25.2 更新了!基于正式发布的 Debian13 Stable!
  • 天津公司网站开发自动生成logo的软件
  • 阿里云多网站门户网站为什么衰落
  • 仓颉开发鸿蒙应用:布局系统的设计哲学与高效实践
  • 嘉兴网站开发公司淘客 wordpress
  • 泰州手机网站制作wordpress修改页面组件
  • 怎么编写一个网站百度高级搜索引擎入口
  • php做网站常见实例手机如何做车载mp3下载网站
  • WebLogic未授权远程命令执行漏洞复现:原理详解+环境搭建+渗透实践(CVE-2020-14882、CVE-2020-14883)
  • 公司响应式网站建设平台中交路桥建设有限公司中标
  • 邢路桥建设总公司网站html网页设计模板免费下载
  • 团队做网站的收获甘肃省城乡和住房建设厅网站
  • 这样做自己公司的网站开发手机app价格
  • 【泛微-注册安全分析报告】
  • 网站建设动画代码软装搭配设计师培训
  • 实现Spring IoC
  • 中国城乡与建设部网站制作网页时一般需要兼容下列选项中的哪些浏览器
  • Vue3 全面学习指南 - 从入门到实战
  • 等保测评取消打分,《网络安全等级测评报告模版(2025版)》重大变更,详细解读两细化、三变更、五新增
  • 响应式商场网站百度公司招聘信息
  • pytorch-数值微分
  • 网站赚钱方法花艺企业网站建设规划
  • 网站开发工具书营销型网站建设项目需求表
  • JAVA的项目复制
  • 关于架构设计的依赖关系
  • 网站优化费用报价明细唐山百度做网站多少钱
  • 旅游门户网站模板下载茂民网站建设
  • ​(吉林版)安全员C证模拟考试练习题与答案
  • 城乡住房建设网站介绍常见的网络营销方式
  • RAG与数据预测的结合应用(1) - 语义相似度的提升