Java架构师-十项全能:打造高度深度广度兼备的全面技术人才网盘分享

#1

download:Java架构师-十项全能:打造高度深度广度兼备的全面技术人才

Golang泛型提高了编码效率

# # # * 前言 *

Golang的泛型已经出来一段时间了,大家应该对它有所了解,甚至在应用中使用过。虽然Golang的泛型功能简单,而且可能会增加代码的复杂度,但是过度使用也可能会降低代码的可读性。
但不可否认的是,泛型确实使我们在使用Golang时能够提取一些常用代码,避免代码的重复拷贝,提高代码性能(避免类型转换),提高编码效率和体验,提高代码可维护性。
本文主要介绍我用Golang泛型做了什么。

# # # * 工具功能 *

虽然标准库中已经提供了大量的工具函数,但是这些工具函数并不是由泛型实现的。为了改善体验,我们可以通过泛型来实现它们。
比如经典的数学。Max(),数学。数值算法中的Min()是float64类型,但是很多时候我们使用这些类型` int,int64 '。在Golang引入泛型之前,我们经常按照如下类型来实现它们,从而产生了大量的模板代码:

func MaxInt(a,b int) int {
如果a > b {
返回a
}
返回b
}

func MaxInt64(a,b int64) int64 {
如果a > b {
返回a
}
返回b
}

//...其他类型

复制代码
使用泛型时,我们只需要一个实现:

func Max[T约束。有序](a,b,T) T {
如果a > b {
返回a
}
返回b
}

复制代码
约束在哪里。Ordered表示可排序的类型,即可以使用三向运算符[>,=,
代码地址
其他如json解析、参数检查、切片等。也可以通过泛型实现。

# # # * 数据结构 *

Golang自己的通用容器是切片和地图。这两种数据结构实际上可以完成大部分工作,但有时我们可能需要其他数据结构,比如优先级队列和链表。
虽然Golang在容器的‘包’下有堆、列表、环三种数据结构,但是用起来说实话不是很方便,尤其是元素类型都是‘接口{ }’。使用这些结构需要各种类型的转换。所以我们可以简单地复制这些代码,然后用泛型对它们进行转换,比如heap:
我们不仅使用泛型来实现,还默认将heap改为slice来实现,所以我们只需要实现一个LessFunc而不是五个。

包堆

类型LessFunc[T any] func(e1 T,e2 T) bool

类型堆[T any]结构{
h []T
lessFunc
}

func New[T any](h []T,lessFunc LessFunc[T]) *Heap[T] {
堆:= &Heap[T]{
h: h,
lessFunc: lessFunc,
}
heap.init()
返回堆
}

//移除堆顶部元素
func (h *Heap[T]) Pop() T {
n := h.Len() - 1
h.swap(0,n)
h.down(0,n)
return h.pop()
}

//获取堆顶部元素
func (h *Heap[T]) Peek() T {
返回h.h[0]
}

//向堆中添加元素
func (h *Heap[T]) Push(x T) {
h.push(x)
h.up(h.Len() - 1)
}

复制代码
代码地址
其他数据结构包括` list、set、pqueue等。

# # # * 模板代码 *

在后台业务代码中,我们经常会有很多业务处理函数,每个业务处理函数基本上都是由一些代码封装成一个HTTP接口。其实都是模板代码。例如,对于gin实现的HTTP服务,我们需要如下处理每个接口:

`指定HTTP方法,URL
证明
参数绑定
处理请求
处理

可以发现参数绑定和处理响应几乎都是同一个模板代码,认证基本都是模板代码(当然有些认证可能比较复杂)。
因此,我们可以编写一个通用模板,并提取相同的部分。用户只需要用不同的接口实现指定的HTTP方法、URL和请求处理逻辑:

//处理请求
func do[Req any,Rsp any,Opt any](reqFunc ReqFunc[Req]),
serviceFunc ServiceFunc[Req,Rsp],serviceopttfunc serviceopttfunc[Req,Rsp,Opt],opts...Opt)杜松子酒。HandlerFunc {
返回函数(c *gin。上下文){
//参数绑定
req,err := BindJSON[Req](c)
如果err!=零{
返回
}
//进一步处理请求结构
if reqFunc!=零{
请求函数
}
var rsp *Rsp
//业务逻辑函数调用
if serviceFunc!=零{
rsp,err = serviceFunc(c,req)
} else if serviceOptFunc!=零{
rsp,err = serviceoptpfunc(c,req,opts...)
}否则{
panic("必须设置ServiceFunc或ServiceFuncOpt ")
}
//处理响应
处理器sp(c,Rsp,err)
}
}

复制代码
这样,现在一个接口基本上只需要一行代码就可以实现(不包括具体的业务逻辑功能):

//简单请求,不需要身份验证
e.GET("/user/info/get ",ginrest。Do(nil,GetUserInfo))
//身份验证、绑定UID、处理
reqFunc := func(c *gin。上下文,req *UpdateUserInfoReq) {
请求。UID = GetUID(c)
}//这里再多一步就是说明第一个参数是ReqFunc。
e.POST("/user/info/update ",Verify,ginrest。Do(reqFunc,UpdateUserInfo))

地址,并实现了一个基于gin的RESTful风格模板。
对象池/缓存
Golang标准库附带了一个线程安全的高性能对象池同步。池,可以根据物体的热度自动释放物体。但是,作为对象池,我们通常只放一种类型的对象进去,但是元素是同步的。池仍然是“接口{}”类型,所以我们可以简单地封装同步。池并使其元素具有特定的类型:
事实上,这里有一个简单的对象同步。Pool进行打包,然后添加一个ClearFunc()在回收对象时做一些清理操作。例如,我们需要使字节片的使用长度为零(容量保持不变)。

//创建一个新对象
键入NewFunc[T any] func() T

//清理对象
类型ClearFunc[T any] func(T) T

类型Pool[T any] struct {
p同步。泳池
clearFunc
}

func New[T any](New func New func[T],clear func clear func[T])* Pool[T]{
如果newFunc == nil {
panic("必须提供NewFunc ")
}
p := &Pool[T]{
clearFunc: clearFunc,
}
p.p.New = func() any {
返回newFunc()
}
返回p
}

//获取对象
func (p *Pool[T]) Get() T {
返回p.p.Get()。(吨)
}

//返回对象
func (p *Pool[T]) Put(t T) {
if p.clearFunc!=零{
t = p.clearFunc(t)
}
投入产出
}

复制代码
用作字节数组对象池:

new func:= func()[]字节{
返回make([]字节,大小,上限)
}
clear func:= func(b[]字节)[]字节{
return b[:0]
}
p := New(newFunc,clearFunc)
Bytes := p.Get() //这里字节的类型是[]byte
页(page的缩写)上传(字节)

复制代码
代码地址
缓存也是如此。目前,大多数缓存库都是基于’ interface{} '或byte[]实现的,但我们仍然更喜欢直接操纵特定的类型,所以我们可以使用泛型来自己实现(或转换)一个缓存库。我还实现了一个通用的缓存策略库,其中包含LRU、LFU、ARC、NearlyLRU、TinyLFU等缓存策略。

# # # * 摘要 *

如你所见,实际上Golang泛型主要提供了代码抽象和封装的能力,使我们能够编写更多可重用的代码,避免到处复制代码,从而提高代码的可维护性和可读性,并从避免类型转换中获得一点点性能提升。