go语言泛型实现 golang 实现泛型
如何看待go语言泛型的最新设计?
Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成为现实。Go 团队实施了一个看起来比较稳定的设计草案,并且正以源到源翻译器原型的形式获得关注。本文讲述的是泛型的最新设计,以及如何自己尝试泛型。
创新互联主营临海网站建设的网络公司,主营网站建设方案,APP应用开发,临海h5小程序制作搭建,临海网站营销推广欢迎临海等地区企业咨询
例子
FIFO Stack
假设你要创建一个先进先出堆栈。没有泛型,你可能会这样实现:
type Stack []interface{}func (s Stack) Peek() interface{} {
return s[len(s)-1]
}
func (s *Stack) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack) Push(value interface{}) {
*s =
append(*s, value)
}
但是,这里存在一个问题:每当你 Peek 项时,都必须使用类型断言将其从 interface{} 转换为你需要的类型。如果你的堆栈是 *MyObject 的堆栈,则意味着很多 s.Peek().(*MyObject)这样的代码。这不仅让人眼花缭乱,而且还可能引发错误。比如忘记 * 怎么办?或者如果您输入错误的类型怎么办?s.Push(MyObject{})` 可以顺利编译,而且你可能不会发现到自己的错误,直到它影响到你的整个服务为止。
通常,使用 interface{} 是相对危险的。使用更多受限制的类型总是更安全,因为可以在编译时而不是运行时发现问题。
泛型通过允许类型具有类型参数来解决此问题:
type Stack(type T) []Tfunc (s Stack(T)) Peek() T {
return s[len(s)-1]
}
func (s *Stack(T)) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack(T)) Push(value T) {
*s =
append(*s, value)
}
这会向 Stack 添加一个类型参数,从而完全不需要 interface{}。现在,当你使用 Peek() 时,返回的值已经是原始类型,并且没有机会返回错误的值类型。这种方式更安全,更容易使用。(译注:就是看起来更丑陋,^-^)
此外,泛型代码通常更易于编译器优化,从而获得更好的性能(以二进制大小为代价)。如果我们对上面的非泛型代码和泛型代码进行基准测试,我们可以看到区别:
type MyObject struct {
X
int
}
var sink MyObjectfunc BenchmarkGo1(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek().(MyObject)
}
}
func BenchmarkGo2(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek()
}
}
结果:
BenchmarkGo1BenchmarkGo1-16 12837528 87.0 ns/op 48 B/op 2 allocs/opBenchmarkGo2BenchmarkGo2-16 28406479 41.9 ns/op 24 B/op 2 allocs/op
在这种情况下,我们分配更少的内存,同时泛型的速度是非泛型的两倍。
合约(Contracts)
上面的堆栈示例适用于任何类型。但是,在许多情况下,你需要编写仅适用于具有某些特征的类型的代码。例如,你可能希望堆栈要求类型实现 String() 函数
尝试用golang 1.18泛型实现orm
这几天golang社区对泛型的讨论非常多的,一片热火朝天的景象。对我们广大gopher来说总归是好事。
泛型很有可能会颠覆我们之前的很多设计,带着这种疑问和冲动,我准备尝试用golang泛型实现几个orm的常见功能。
本文并没完全实现通用的orm,只是探讨其实现的一种方式提供各位读者做借鉴。
虽然golang有了泛型,但是目前在标准库sql底层还没有改造,目前还有很多地方需要用到reflect。
调用方式
这个部分跟传统的orm使用上没有太大区别,没办法不使用反射的情况下,泛型的方式可能变得有点繁琐。
调用方式
和创建table类似,写入数据好像比没有之前的orm有优势。
读取数据是非常高频的操作,所以我们稍作封装。
调用方式
稍微比原先的orm方式有了多一点想象空间,比如 在[T any]做更明确的约束,比如要求实现Filter定制方法。
鉴于本人能力还认证有限,目前还没有发现泛型对orm剧烈的改进和突破的可能。未来如果go对底层sql做出改动,或者实现诸如Rust那种Enum方式,可能会带来更多的惊喜。
golang泛型实现——skiplist
skiplist是一种有序的数据结构, 不同于各种平衡树, skiplist看起来就是多层的链表, 具体点每个元素是个数组, 这个元素的数组除了0层是和下个元素直连, 1层和n层之间可能和下个, 或者下下个节点连接起来。
这些skiplist节点的多层结构,构成实施二分搜索的基础, 理论从而达到可观的效率, 开源界大名鼎鼎的redis的zset一部分使用skiplist。
对于这个被吹爆了的数据,下面会使用redis的套路在go里面实现下, 是骡子是马拉出来溜溜压测下性能如何。
表头和节点
还是以上面的skiplist为例, 下面会画出查找每个元素的搜索路径。
skiplist里面head节点有个两个重要的特点:
下面的查找路径,
查找的过程就是先从head节点的 MaxLevel-1 索引查找, 查找的大致走位是向右向下向右向下...就逼近目标位置
新增先使用 update[] 数组维护每层需要插入节点的位置, 通过抛硬币的函数决定这个新节点的level, 最后修改节点的前后关系, 就是插入链表节点的多层版本, 维护多层信息就是 update[] 干的事情。
把1当成硬币的正面, 0当作硬币的反面, 直到抛到0时就结束, 这时连续的正面就是skiplist里面需要建的level数。
如果在单链表中执行插入操作
skiplist的插入和单链表相似, 无非是扩展到多层, 使用一个数组记录每一层的prev指针,skiplist的新节点也是数组,这里使用一个for循环遍历层,每个层内的操作与单链表中的操作是一样的。
插入节点5
如果在单链表中执行删除操作, prev是待删除节点的前面一个节点, 如果要删除待删除节点n, 直接prev.next = n.next就完成一个节点的删除, skiplist的删除也和单链表类似。
删除节点5
从压测上来看, 本文中的skiplist的实现, 相比golang map性能接近,同时保持有序特性,值得王婆卖瓜...
有小伙伴对完整实现感兴趣的可查看如下链接, 除了上面聊过的Get、Set、Remove外, 还有遍历、TopMax、TopMin等操作。
GO语言(十五):泛型入门(下)-
在本节中,您将添加通用函数调用的修改版本,进行小的更改以简化调用代码。您将删除在这种情况下不需要的类型参数。
当 Go 编译器可以推断您要使用的类型时,您可以在调用代码中省略类型参数。编译器从函数参数的类型推断类型参数。
请注意,这并不总是可能的。例如,如果您需要调用没有参数的泛型函数,则需要在函数调用中包含类型参数。
在 main.go 中,在您已有的代码下方,粘贴以下代码。
在此代码中:
(1)调用泛型函数,省略类型参数。
从包含 main.go 的目录中的命令行,运行代码。
接下来,您将通过将整数和浮点数的并集捕获到您可以重用的类型约束(例如从其他代码中)来进一步简化函数。
正如您将在本节中看到的,约束接口也可以引用特定类型。
1、编写代码
在此代码中:
b.在您已有的函数下方,粘贴以下通用 SumNumbers函数。
在此代码中:
c.在 main.go 中,在您已有的代码下方,粘贴以下代码。
在此代码中:
(1)调用SumNumbers打印每个map的总和。
与上一节一样,在调用泛型函数时省略了类型参数(方括号中的类型名称)。Go 编译器可以从其他参数推断类型参数。
从包含 main.go 的目录中的命令行,运行代码。
做得很好!您刚刚学习了 Go 中的泛型。
当前文章:go语言泛型实现 golang 实现泛型
文章源于:http://pwwzsj.com/article/ddsdjds.html