# README
go-extend
go-extend 收集一些常用的操作函数,辅助更快的完成开发工作,并减少重复代码。
它收集各种杂项函数,并进行归类,方便使用者查找,它可以大幅度提升开发效率和程序运行性能。它以保证性能为最大前提,提供有效的方法。 针对一些标准库中的函数或者库进行一些修改,使其性能大幅度提升,但它并不用来替换标准库函数,这些函数往往会在一些场景下有效,但有些函数可以用来替换标准库函数,它们保持一致的功能,且相当安全。
一些包或者函数使用示例及分析可以在我的 博客(https://blog.thinkeridea.com) 中找到。
安装
$ go get github.com/thinkeridea/go-extend/...
规范:
- 与标准库包名一致的使用
ex
前缀, 避免与标准库包冲突 - 包目录下
doc.go
作为包说明文档
性能测试
包中一些函数会进行性能测试,包括每次修订的性能对比,它们一般位于各自包下面的 benchmark
目录下,性能测试结果可以在 benchmark.md 快速浏览。
标准库函数改进列表
用来替换标准库的函数,它们和标准库函数功能保持一致,并拥有更好的性能:
- exstrings.Join 该方法是对标准库 strings.Join 修改,配合 unsafe 包能有效减少内存分配
- exstrings.Repeat 该方法是对标准库 strings.Repeat 修改,对于创建大字符串能有效减少内存分配
- exstrings.Replace 替换字符串 该方法是对标准库 strings.Replace 修改,配合 unsafe 包能有效减少内存分配
用该改善标准库的函数,它们基本和标准库功能一致,但是它们都拥有更好的性能:
- exbytes.Replace 它使用原地替换,直接修改输入的数据,获得更好的性能
- exstrings.JoinToBytes 它响应一个
[]byte
类型,有效避免类型转换 - exstrings.RepeatToBytes 它响应一个
[]byte
类型,有效避免类型转换 - exstrings.ReplaceToBytes 它响应一个
[]byte
类型,有效避免类型转换 - exstrings.UnsafeReplaceToBytes 它响应一个
[]byte
类型,并进行原地替换,它不能接收一个字面量字符串,否则会发生严重错误
许可
go-extend 根据 MIT License 许可证授权,有关完整许可证文本,请参阅 LICENSE
# Packages
Package datalog 用于辅助拼接类 csv 格式化数据日志的组件。
Record 作为一行记录或者一组数据,可以简化数据字段拼接过程及避免低级错误,
详细可以参考博客:https://blog.thinkeridea.com/201907/go/csv_like_data_logs.html
使用示例:
const (
LogVersion = "v1.0.0"
)
const (
LogVer = iota
LogTime
LogUid
LogUserName
LogFriends
LogFieldNumber
)
func main() {
var w bytes.Buffer
pool := datalog.NewRecordPool(LogFieldNumber)
r := pool.Get().(datalog.Record)
r[LogVer] = LogVersion
r[LogTime] = time.Now().Format("2006-01-02 15:04:05")
r[LogUid] = "Uid"
r[LogUserName] = "UserNmae"
data := r.Join(datalog.FieldSep, datalog.NewLine)
r.Clean()
pool.Put(r)
if _, err := w.Write(data); err != nil {
panic(err)
}
}
这会输出:(因为分隔符是不可见字符,下面使用,代替字段分隔符,使用;\n代替换行符, 使用/代替数组字段分隔符,是-代替数组分隔符)
'v1.0.0,2019-07-18,11:39:09,Uid,UserNmae,;\n'
往往我们会先定义一组常量, 用来标记字段的顺序,常量标识数据位置,保证我们不会因为一些检查或者忘记记录某个字段而导致整个日志格式混乱,
没有赋值的 LogFriends 依旧有一个占位符,确保日志格式完好。
使用 pool 可以很好的利用内存,不会带来过多的内存分配,而且 Record 的每个字段值都是字符串,简单的赋值并不会带来太大的开销。
使用 Record.Join 可以高效的连接一行日志记录,便于我们快速的写入的日志文件中。
有时候也并非都是记录一些单一的值,比如上面 LogFriends 会记录当前记录相关的朋友信息,这可能是一组数据,
datalog 也提供了一些简单的辅助函数,可以结合下面的实例实现:
const (
LogVersion = "v1.0.0"
)
const (
LogVer = iota
LogTime
LogUid
LogUserName
LogFriends
LogFieldNumber
)
const (
LogFriendUid = iota
LogFriendUserName
LogFriendFieldNumber
)
func main() {
var w bytes.Buffer
pool := datalog.NewRecordPool(LogFieldNumber)
frPool := datalog.NewRecordPool(LogFriendFieldNumber)
r := pool.Get().(datalog.Record)
r[LogVer] = LogVersion
r[LogTime] = time.Now().Format("2006-01-02 15:04:05")
r[LogUid] = "Uid"
r[LogUserName] = "UserNmae"
r[LogFriends] = GetLogFriends(rand.Intn(3), frPool)
data := r.Join(datalog.FieldSep, datalog.NewLine)
r.Clean()
pool.Put(r)
if _, err := w.Write(data); err != nil {
panic(err)
}
fmt.Println("'" + w.String() + "'")
}
func GetLogFriends(friendNum int, pool *sync.Pool) string {
fs := datalog.NewRecord(friendNum)
fr := pool.Get().(datalog.Record)
for i := 0; i < friendNum; i++ {
fr[LogFriendUid] = "FUid"
fr[LogFriendUserName] = "FUserName"
fs[i] = fr.ArrayFieldJoin(datalog.ArrayFieldSep, datalog.ArraySep)
}
fr.Clean()
pool.Put(fr)
return fs.ArrayJoin(datalog.ArraySep)
}
这会输出:(因为分隔符是不可见字符,下面使用,代替字段分隔符,使用;\n代替换行符, 使用/代替数组字段分隔符,是-代替数组分隔符)
'v1.0.0,2019-07-18,11:39:09,Uid,UserNmae,FUid/FUserName-FUid/FUserName;\n'
这样在解析时可以把某一字段当做数组解析,这极大的极大的提高了数据日志的灵活性,
但是并不建议使用过多的层级,数据日志应当清晰简洁,但是有些特殊场景可以使用一层嵌套。
*/.
No description provided by the author
Package exatomic 实现了 float32 与 float64 原子操作,
我在历史项目中实时在线统计一些价格,在多线程时往往非常有用,
简化程序逻辑,并提升程序性能。
float 原子操作可以设计很多个版本,历史项目中我用过 3 种不同的实现方式,
当前项目是我最满意的版本,对程序没有任何入侵,也没有定义新的类型,保持和标准库一致的接口,非常的简单。
使用该包你必须了解到,有时间你的操作可能并不像预期一样,受 float 类型算法实现,float 精度往往并没有想象中那么高,
如果连续对一个数加一个指定值,可能最后你会得到一个固定的值,它永远不会增加,例如下面的程序:
var x float64
for delta := float64(1); delta+delta > delta; delta += delta {
x = delta
if x+1 == x {
fmt.Printf("%.64f\n", x)
}
}
输出的结果有点令人意外, x+1以后居然不会有任何变化,而且这样值有非常多,
根据不同初始值,和增加不同大小的数据,出现这种情况的数值也不同。
以下是一部分输出:
9007199254740992.0000000000000000000000000000000000000000000000000000000000000000
18014398509481984.0000000000000000000000000000000000000000000000000000000000000000
36028797018963968.0000000000000000000000000000000000000000000000000000000000000000
72057594037927936.0000000000000000000000000000000000000000000000000000000000000000
144115188075855872.0000000000000000000000000000000000000000000000000000000000000000
288230376151711744.0000000000000000000000000000000000000000000000000000000000000000
576460752303423488.0000000000000000000000000000000000000000000000000000000000000000
1152921504606846976.0000000000000000000000000000000000000000000000000000000000000000
2305843009213693952.0000000000000000000000000000000000000000000000000000000000000000
4611686018427387904.0000000000000000000000000000000000000000000000000000000000000000
9223372036854775808.0000000000000000000000000000000000000000000000000000000000000000
.........
Package exbytes 收集常规的 []byte 操作,作为 go 标准库 bytes 的扩展。 避免重复编写 []byte 相关操作代码,集中在一起更有利于优化代码,保证代码质量。
这个包会使用一些特殊的操作来减少内存开销,已获得更好的程序性能。
我也会在这个包重写 标准库 bytes 里面的一些方法,以适用于不同场景的性能优化。.
Package exmath 对 math 的扩展.
Package exnet 收集一些网络模块函数.
Package exstrings 收集常规的 string 操作,作为 go 标准库 strings 的扩展。 避免重复编写 string 相关操作代码,集中在一起更有利于优化代码,保证代码质量。
这个包会使用一些特殊的操作来减少内存开销,已获得更好的程序性能。
我也会在这个包重写 标准库 strings 里面的一些方法,以适用于不同场景的性能优化。.
Package exsync 对 sync 的扩展.
Package extime 收集时间操作或计算的方法,以及有关不同国家地域的特殊日期方法。
根据一些习惯会创建一些常用的常量,简化程序书写,特别是格式化方面的。.
No description provided by the author
Package helper 收集一些可以辅助我们快速编程的方法,让我们极快的编写出自己的应用程序。 一些脚手架函数在特殊的情况让我少处理很多程序问题,还能标准化我们编写程序的方式,我认为这是极好的。
该函数可能会限制一些自定义的东西,如果你喜欢完全可以用在自己的程序中, 如果你不是很喜欢这种处理方法, 你可以拷贝该函数的在自己的程序中创建新函数,并去改善和定制它的处理方式。.
Package pool 创建一些常用的 pool, 底层依托 sync.pool
在项目开发中会大量使用 sync.Pool, 有很多 pool 是可以共享的,最常见的就是 bytes.Buffer。 大量的开源库中在使用 bytes.Buffer 的 sync.Pool, 每个项目单独维护自己的 sync.Pool, 使得很多资源没有被合理的利用, 造成大量的浪费,这个包收集常见的 sync.Pool 实例,使其可以在不同项目和包中可以共享。.