泛型映射(推荐使用)
从 v2.10 版本开始,gmap 提供了泛型映射 KVMap[K, V]、ListKVMap[K, V] 和 TreeKVMap[K, V],提供类型安全的映射操作。开发者可以使用 NewKVMap[string, int]() 等方式创建指定类型的映射,避免类型断言和运行时错误。
泛型哈希映射 KVMap
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
// 创建一个键为string,值为int的泛型哈希映射
m := gmap.NewKVMap[string, int]()
// 设置键值对
m.Set("apple", 1)
m.Set("banana", 2)
m.Set("orange", 3)
// 获取值
if value, ok := m.Get("apple"); ok {
fmt.Println("apple:", value) // 输出: apple: 1
}
// 获取所有键值
fmt.Println("Keys:", m.Keys())
fmt.Println("Values:", m.Values())
// 遍历
m.Iterator(func(k string, v int) bool {
fmt.Printf("%s: %d\n", k, v)
return true
})
}
泛型有序映射 ListKVMap
ListKVMap 是按照插入顺序维护键值对的有序映射。
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
// 创建有序映射
m := gmap.NewListKVMap[string, int]()
// 按顺序插入
m.Set("third", 3)
m.Set("first", 1)
m.Set("second", 2)
// 遍历会按插入顺序输出
m.Iterator(func(k string, v int) bool {
fmt.Printf("%s: %d\n", k, v)
return true
})
// Output:
// third: 3
// first: 1
// second: 2
}
泛型树形映射 TreeKVMap
TreeKVMap 使用红黑树实现,键值对按照比较函数定义的顺序排序。
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/util/gutil"
)
func main() {
// 创建树形映射,使用字符串比较函数
m := gmap.NewTreeKVMap[string, int](gutil.ComparatorString)
// 插入数据
m.Set("banana", 2)
m.Set("apple", 1)
m.Set("orange", 3)
// 遍历会按照键的字母序输出
m.Iterator(func(k string, v int) bool {
fmt.Printf("%s: %d\n", k, v)
return true
})
// Output:
// apple: 1
// banana: 2
// orange: 3
}
自定义 nil 值检查器
NilChecker 是一个可选参数,并非必须提供。它主要用于解决typed nil问题,并提供更好的nil判断性能。在默认情况下,组件使用反射来判断数据是否为nil。
对于指针类型、接口类型等,可能需要自定义 nil 判断逻辑。NewKVMapWithChecker 系列函数支持自定义 nil 检查器。
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
type User struct {
ID int
Name string
}
func main() {
// 为指针类型创建映射,使用自定义nil检查器
m := gmap.NewKVMapWithChecker[string, *User](func(u *User) bool {
return u == nil || u.ID == 0
})
m.Set("user1", &User{ID: 1, Name: "John"})
m.Set("user2", &User{ID: 0, Name: "Invalid"}) // 会被视为nil
// GetOrSet在值为nil时会设置新值
user := m.GetOrSet("user2", &User{ID: 2, Name: "Jane"})
fmt.Printf("user2: %+v\n", user)
// Output:
// user2: &{ID:2 Name:Jane}
}
泛型映射特点
- 类型安全:在编译期就能发现类型错误,避免运行时类型断言失败。
- 自定义
nil检查(可选):通过NewXXXWithChecker系列函数,可以为泛型类型自定义nil判断逻辑,解决类型化nil的问题。默认情况下使用反射判断。 - 性能优势:减少了类型断言和反射的开销。
- 更好的IDE支持:IDE能够提供更准确的代码补全和类型提示。
推荐在新项目中使用泛型映射 KVMap[K, V]、ListKVMap[K, V] 和 TreeKVMap[K, V],它们提供了更好的类型安全性和开发体验。
传统映射类型
下面介绍传统的映射类型,这些类型在旧版本中被广泛使用,仍然完全支持。
并发安全
gmap 支持并发安全选项开关,在默认情况下是 非并发安全 的,开发者可以选择开启 gmap 的并发安全特性(传递初始化开关参数 safe 参数值为 true, 必须在初始化时设定,不能运行时动态设定)。如:
m := gmap.New(true)
不仅仅是 gmap 模块, goframe 框架的其他并发安全数据结构也支持并发安全特性开关。
基本使用
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
// 创建一个默认的gmap对象,
// 默认情况下该gmap对象不支持并发安全特性,
// 初始化时可以给定true参数开启并发安全特性。
m := gmap.New()
// 设置键值对
for i := 0; i < 10; i++ {
m.Set(i, i)
}
// 查询大小
fmt.Println(m.Size())
// 批量设置键值对(不同的数据类型对象参数不同)
m.Sets(map[interface{}]interface{}{
10 : 10,
11 : 11,
})
fmt.Println(m.Size())
// 查询是否存在
fmt.Println(m.Contains(1))
// 查询键值
fmt.Println(m.Get(1))
// 删除数据项
m.Remove(9)
fmt.Println(m.Size())
// 批量删除
m.Removes([]interface{}{10, 11})
fmt.Println(m.Size())
// 当前键名列表(随机排序)
fmt.Println(m.Keys())
// 当前键值列表(随机排序)
fmt.Println(m.Values())
// 查询键名,当键值不存在时,写入给定的默认值
fmt.Println(m.GetOrSet(100, 100))
// 删除键值对,并返回对应的键值
fmt.Println(m.Remove(100))
// 遍历map
m.Iterator(func(k interface{}, v interface{}) bool {
fmt.Printf("%v:%v ", k, v)
return true
})
// 自定义写锁操作
m.LockFunc(func(m map[interface{}]interface{}) {
m[99] = 99
})
// 自定义读锁操作
m.RLockFunc(func(m map[interface{}]interface{}) {
fmt.Println(m[99])
})
// 清空map
m.Clear()
// 判断map是否为空
fmt.Println(m.IsEmpty())
}
执行后,输出结果为:
12
true
1
11
9
[0 1 2 4 6 7 3 5 8]
[3 5 8 0 1 2 4 6 7]
100
100
3:3 5:5 8:8 7:7 0:0 1:1 2:2 4:4 6:6 99
true
有序遍历
我们来看一下三种不同类型 map 的有序性遍历示例。
package main
import (
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/util/gutil"
)
func main() {
array := g.Slice{2, 3, 1, 5, 4, 6, 8, 7, 9}
hashMap := gmap.New(true)
listMap := gmap.NewListMap(true)
treeMap := gmap.NewTreeMap(gutil.ComparatorInt, true)
for _, v := range array {
hashMap.Set(v, v)
}
for _, v := range array {
listMap.Set(v, v)
}
for _, v := range array {
treeMap.Set(v, v)
}
fmt.Println("HashMap Keys:", hashMap.Keys())
fmt.Println("HashMap Values:", hashMap.Values())
fmt.Println("ListMap Keys:", listMap.Keys())
fmt.Println("ListMap Values:", listMap.Values())
fmt.Println("TreeMap Keys:", treeMap.Keys())
fmt.Println("TreeMap Values:", treeMap.Values())
}
执行后,输出结果为:
HashMap Keys: [4 6 8 7 9 2 3 1 5]
HashMap Values: [6 8 4 3 1 5 7 9 2]
ListMap Keys: [2 3 1 5 4 6 8 7 9]
ListMap Values: [2 3 1 5 4 6 8 7 9]
TreeMap Keys: [1 2 3 4 5 6 7 8 9]
TreeMap Values: [1 2 3 4 5 6 7 8 9]
FilterEmpty/FilterNil 空值过滤
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
m1 := gmap.NewFrom(g.MapAnyAny{
"k1": "",
"k2": nil,
"k3": 0,
"k4": 1,
})
m2 := gmap.NewFrom(g.MapAnyAny{
"k1": "",
"k2": nil,
"k3": 0,
"k4": 1,
})
m1.FilterEmpty()
m2.FilterNil()
fmt.Println(m1.Map())
fmt.Println(m2.Map())
// Output:
// map[k4:1]
// map[k1: k3:0 k4:1]
}
Flip 键值对反转
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
})
m.Flip()
fmt.Println(m.Map())
// May Output:
// map[v1:k1 v2:k2]
}
Keys/Values 键名/数值列表
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
fmt.Println(m.Keys())
fmt.Println(m.Values())
// May Output:
// [k1 k2 k3 k4]
// [v2 v3 v4 v1]
}
Pop/Pops 随机出栈
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
fmt.Println(m.Pop())
fmt.Println(m.Pops(2))
fmt.Println(m.Size())
// May Output:
// k1 v1
// map[k2:v2 k4:v4]
// 1
}
SetIfNotExist* 判断性写入
判断性写入是指当指定的键名不存在时则写入并且方法返回 true,否则忽略写入并且方法返回 false。相关方法如下:
SetIfNotExistSetIfNotExistFuncSetIfNotExistFuncLock
方法具体描述请查看接口文档或源码注释。
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
var m gmap.Map
fmt.Println(m.SetIfNotExist("k1", "v1"))
fmt.Println(m.SetIfNotExist("k1", "v1"))
fmt.Println(m.Map())
// Output:
// true
// false
// map[k1:v1]
}
Merge 字典表合并
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
var m1, m2 gmap.Map
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(&m2)
fmt.Println(m1.Map())
// May Output:
// map[key1:val1 key2:val2]
}
JSON 序列化/反序列
gmap 模块下的所有容器类型均实现了标准库 json 数据格式的序列化/反序列化接口。
1. Marshal
package main
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
m := gmap.New()
m.Sets(g.MapAnyAny{
"name": "john",
"score": 100,
})
b, _ := json.Marshal(m)
fmt.Println(string(b))
}
执行后,输出结果:
{"name":"john","score":100}
Unmarshal
package main
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
m := gmap.Map{}
s := []byte(`{"name":"john","score":100}`)
json.Unmarshal(s, &m)
fmt.Println(m.Map())
}
执行后,输出结果:
map[name:john score:100]