每日一Go-34、Go深入-泛型高阶实战--泛型工具库、泛型容器、泛型算法

0 阅读5分钟

图片

***文末有源码下载链接***

一、为什么需要泛型高阶实战?

写泛型不难,难的是写”好“泛型。

你会遇到如下情况:

  • 如何设计一个真正可复用的约束?

  • 一个泛型容器应该如何实现并发安全、零拷贝、友好API?

  • 如何抽象出可复用算法而不是写一堆if/else?

  • 如何让泛型代码不慢?

今天的内容旧让你做到:能写工具库、能写自己的容器、能写可复用的算法。

二、构建泛型工具库

    工具库是泛型最适合发挥的地方:类型无关、逻辑可复用

    2.1 Maybe[T]:一个常用的函数式泛型工具

        类似Option类型,避免nil

type Maybe[T any] struct {
    v  T
    ok bool
}
func Some[T any](v T) Maybe[T] { return Maybe[T]{v, true} }
func None[T any]() Maybe[T]    { return Maybe[T]{ok: false} }
func (m Maybe[T]) Get() (T, bool) { return m.v, m.ok }
func (m Maybe[T]) Or(defaultV T) T {
    if m.ok {
        return m.v
    }
    return defaultV
}
// 查找map中是否存在key
func lookupMaybe[T comparable, U any](m map[T]U, k T) Maybe[U] {
    if v, ok := m[k]; ok {
        return Some(v)
    }
    return None[U]()
}
users := map[int]string{
        100: "Codee君",
        200: "詹姆斯邦德",
        300: "乔治华盛顿",
    }
    res1 := lookupMaybe(users, 100)
    if name, ok := res1.Get(); ok {
        fmt.Println(name)
    }
    res2 := lookupMaybe(users, 011)
    nameOrDefault := res2.Or("未知")
    fmt.Println(nameOrDefault)
    nameOrDefault2 := res1.Or("未知")
    fmt.Println(nameOrDefault2)
// 输出
Codee君
未知
Codee君

    Maybe[T] 类型的主要优势在于它使代码流更清晰,特别是通过 Or() 这样的方法,将 缺失值处理逻辑(提供默认值)与操作逻辑(查找) 优雅地结合起来,避免了每次查找后都写 if ok { ... } else { ... } 这样的样板代码。

    2.2  转换函数与过滤器

package main
// 切片A转切片B
func SliceA2B[A any, B any](a []A, f func(A) B) []B {
    b := make([]B, len(a))
    for i, v := range a {
        b[i] = f(v)
    }
    return b
}
// 切片过滤
func Filter[T any](in []T, f func(T) bool) []T {
    out := make([]T, 0)
    for _, v := range in {
        if f(v) {
            out = append(out, v)
        }
    }
    return out
}
func main() {
    fmt.Println("Hello, Codee君!")
    numbers := []int{1, 2, 3, 4, 5}
    // 转换成字符串
    strings := SliceA2B(numbers, func(i int) string {
        return fmt.Sprintf("ID=%d", i)
    })
    fmt.Println(strings)
    // 过滤出偶数
    even := Filter(numbers, func(i int) bool {
        return i%2 == 0
    })
    fmt.Println(even)
}
//输出
Hello, Codee君!
[ID=1 ID=2 ID=3 ID=4 ID=5]
[2 4]

三、泛型容器

    容器是泛型最能发挥威力的地方。

    3.1 泛型栈:并发安全,零拷贝,API友好

type Stack[T any] struct {
    data []*T
    mu   sync.Mutex
    len  atomic.Int64
}
func NewStack[T any]() *Stack[T] {
    return &Stack[T]{
        data: make([]*T, 0),
    }
}
func (s *Stack[T]) Push(v *T) {
    s.mu.Lock()
    defer s.mu.Unlock()
    // 避免元素复制:直接存储指针*T
    s.data = append(s.data, v)
    s.len.Add(1)
}
func (s *Stack[T]) Pop() *T {
    s.mu.Lock()
    defer s.mu.Unlock()
    if s.len.Load() == 0 {
        return nil
    }
    v := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    s.len.Add(-1)
    return v
}
// 辅助API
// 返回栈当前元素数量
func (s *Stack[T]) Len() int64 {
    return s.len.Load()
}
// 栈
    stack := NewStack[string]()
    wg := sync.WaitGroup{}
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            value := fmt.Sprintf("value-%d", i)
            stack.Push(&value)
        }(i)
    }
    wg.Wait()
    fmt.Println("栈元素数量:", stack.Len())
    if itm, ok := stack.Pop(); ok {
        fmt.Println(*itm)
        fmt.Println("栈元素数量:", stack.Len())
    }
// 输出
栈元素数量: 100
value-99
栈元素数量: 99
目标达成情况
并发安全使用sync.Mutex
零拷贝通过存储指针(*T),避免了在Push/Pop时的元素值复制
友好API提供了Push,Pop,Len等简洁方法

     3.2 并发安全的泛型Map

type CodeeMap[K comparable, V any] struct {
    data map[K]V
    mu   sync.RWMutex
}
func NewCodeeMap[K comparable, V any]() *CodeeMap[K, V] {
    return &CodeeMap[K, V]{
        data: make(map[K]V),
    }
}
func (m *CodeeMap[K, V]) Set(k K, v V) {
    m.mu.Lock()
    defer m.mu.Unlock()
    m.data[k] = v
}
func (m *CodeeMap[K, V]) Get(k K) (V, bool) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    v, ok := m.data[k]
    return v, ok
}
// 映射
    m := NewCodeeMap[int, string]()
    m.Set(1, "ID=1")
    m.Set(2, "ID=2")
    m.Set(3, "ID=3")
    fmt.Println(m.Get(1))
    fmt.Println(m.Get(2))
    fmt.Println(m.Get(3))
    fmt.Println(m.Get(4)) 
// 输出
ID=1 true
ID=2 true
ID=3 true
 false

四、泛型算法

    4.1 定制可排序泛型函数

// 需要导入内置包golang.org/x/exp/constraints
// 定制可排序约束
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}
// Ordered的意义:支持数字、string、rune等,只要是可用比大小的类型都可以

    4.2 泛型二分查找

// 泛型二分查找
func BinarySearch[T constraints.Ordered](arr []T, target T) int {
    left, right := 0, len(arr)-1
    for left <= right {
        mid := left + (right-left)/2
        if arr[mid] == target {
            return mid
        } else if arr[mid] < target {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}

4.3 泛型归约运算

// 泛型归约算法
func Reduce[T any, R any](arr []T, init R, f func(R, T) R) R {
    acc := init
    for _, v := range arr {
        acc = f(acc, v)
    }
    return acc
}
sum := Reduce([]int{1, 2, 3, 4}, 0, func(a, b int) int { return a + b })
    fmt.Println(sum)
    mult := Reduce([]int{1, 2, 3, 4}, 1, func(a, b int) int { return a * b })
    fmt.Println(mult)
//输出
10
24

   4.4 泛型优先队列

// 泛型优先队列
type Item[T any] struct {
    value    T
    Priority int
}
type PriorityQueue[T any] []*Item[T]
func (pq PriorityQueue[T]) Len() int {
    return len(pq)
}
func (pq PriorityQueue[T]) Less(i, j int) bool {
    return pq[i].Priority < pq[j].Priority
}
func (pq PriorityQueue[T]) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueue[T]) Push(x interface{}) {
    item := x.(*Item[T])
    *pq = append(*pq, item)
}
func (pq *PriorityQueue[T]) Pop() interface{} {
    old := *pq
    n := len(old)
    if n == 0 {
        var zero T
        return zero
    }
    item := old[n-1]
    *pq = old[0 : n-1]
    return item
}
func NewPriorityQueue[T any]() *PriorityQueue[T] {
    return &PriorityQueue[T]{}
}
type Task struct {
    Name    string
    DueDate time.Time
}
import "container/heap"
// 优先队列
    pq := make(PriorityQueue[Task], 0)
    tasks := []Task{
        {
            Name:    "任务1",
            DueDate: time.Now(),
        },
        {
            Name:    "任务2",
            DueDate: time.Now().AddDate(0, 0, 2),
        },
        {
            Name:    "任务3",
            DueDate: time.Now().AddDate(0, 0, 1),
        },
    }
    for _, task := range tasks {
        // 将Task包装在Item中,并根据DueDate设置优先级(时间越早优先级越高)
        priority := int(time.Since(task.DueDate).Milliseconds())
        if priority < 0 {
            priority = -priority // 确保过期时间越早,优先级越高
        }
        heap.Push(&pq, &Item[Task]{
            value:    task,
            Priority: priority,
        })
    }
    for pq.Len() > 0 {
        item := heap.Pop(&pq).(*Item[Task])
        fmt.Println(item.value)
    }
//输出
{任务1 2025-12-02 11:01:56.3726019 +0800 CST m=+0.001558601}
{任务3 2025-12-03 11:01:56.4072894 +0800 CST}
{任务2 2025-12-04 11:01:56.3726019 +0800 CST}

五、性能:泛型是不是会变慢?

    泛型不会让性能变差,通常比空接口更快。原因是:

  • 泛型是编译期静态展开的

  • 没有运行时类型断言

  • 没有接口装箱

  • 编译器能内联

    在容器、工具库中几乎都是净性能收益。

六、总结

    以上这些模式广泛应用于:微服务框架、数据库驱动、ORM、业务工具库、缓存系统、并发数据结构,掌握这些方法,对你今后的工作和学习有巨大帮助!

*源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!