Go 语言:使用 map[string]struct{} 实现 Set

🔹 struct{} 是什么?

struct{}空结构体,Go 中最小的类型:

1
2
var s struct{}
fmt.Println(unsafe.Sizeof(s)) // 0 —— 占用 0 字节内存
  • 它只有一个可能的值:struct{}{}(空结构体的字面量)
  • 不占用任何内存空间,是理想的”占位符”类型

🔹 为什么用它做 Set?

Set(集合)只关心 key 存不存在,value 没有实际意义。对比两种写法:

1
2
3
4
5
// 方式一:map[string]bool — value 占 1 字节
seen := map[string]bool{"alice": true, "bob": true}

// 方式二:map[string]struct{} — value 占 0 字节 ✅ 更省内存
seen := map[string]struct{}{"alice": {}, "bob": {}}

💡 当 Set 元素数量很大时(如百万级),使用 struct{} 可显著减少内存占用。


🔹 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 创建
set := make(map[string]struct{})

// 添加元素
set["alice"] = struct{}{}

// 判断是否存在(逗号 ok 惯用法)
if _, ok := set["alice"]; ok {
fmt.Println("存在")
}

// 删除元素
delete(set, "alice")

// 遍历
for key := range set {
fmt.Println(key)
}

// 获取长度
fmt.Println(len(set))

// 清空 Set(重新初始化)
set = make(map[string]struct{})

🔹 两种方式对比

对比项 map[string]bool map[string]struct{}
每个 value 内存 1 字节 0 字节
添加元素 m["k"] = true m["k"] = struct{}{}
判断存在 if m["k"] if _, ok := m["k"]; ok
可读性 ✅ 更直观 struct{}{} 写起来略丑
适用场景 元素少,追求简洁 元素多,追求省内存

经验法则:小 Set 用 bool 更简洁,大 Set 用 struct{} 更省内存。实际项目中两种都很常见。


🔹 实用示例

✅ 去重

1
2
3
4
5
6
7
8
9
10
11
12
func unique(strings []string) []string {
seen := make(map[string]struct{}, len(strings))
result := make([]string, 0, len(strings))

for _, s := range strings {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
}
}
return result
}

✅ 集合交集

1
2
3
4
5
6
7
8
9
func intersection(a, b map[string]struct{}) map[string]struct{} {
result := make(map[string]struct{})
for k := range a {
if _, ok := b[k]; ok {
result[k] = struct{}{}
}
}
return result
}

✅ 集合并集

1
2
3
4
5
6
7
8
9
10
func union(a, b map[string]struct{}) map[string]struct{} {
result := make(map[string]struct{})
for k := range a {
result[k] = struct{}{}
}
for k := range b {
result[k] = struct{}{}
}
return result
}

🔹 进阶技巧

🎯 预分配容量(提升性能)

1
2
// 如果知道大概元素数量,预分配容量可减少 map 扩容开销
set := make(map[string]struct{}, 1000)

🎯 封装成 Set 类型(提升可读性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type StringSet map[string]struct{}

func NewStringSet(items ...string) StringSet {
s := make(StringSet, len(items))
for _, item := range items {
s.Add(item)
}
return s
}

func (s StringSet) Add(item string) {
s[item] = struct{}{}
}

func (s StringSet) Has(item string) bool {
_, ok := s[item]
return ok
}

func (s StringSet) Remove(item string) {
delete(s, item)
}

// 使用示例
set := NewStringSet("alice", "bob")
if set.Has("alice") {
fmt.Println("alice 在集合中")
}

🔑 关键要点速记

要点 说明
struct{} 大小 0 字节,最省内存的占位类型
唯一值 struct{}{} 是其唯一合法字面量
添加元素 set["k"] = struct{}{}
判断存在 必须用 _, ok := set["k"],不能直接用值判断
内存优势 百万级元素时,比 bool 节省约 1MB+ 内存
可读性权衡 小项目用 bool 更直观,大项目用 struct{} 更高效

📌 一句话总结map[string]struct{} 是 Go 中实现 Set 的内存最优方案,理解其零内存占位特性,能在高性能场景中显著降低资源消耗。