[持续更新]Go中常用函数集与常见错误
前言
本文汇集了,go开发中常用的函数集, 在日常开发中可以直接拿来进行使用。包括md5加密,SHA256生成哈希值,将body中=号格式的字符串转为map,关于函数的返回建议。
SHA256生成哈希值
func GetSHA256HashCode(file *os.File) string {
//创建一个基于SHA256算法的hash.Hash接口的对象
hash := sha256.New()
_, _ = io.Copy(hash, file)
//计算哈希值
bytes := hash.Sum(nil)
//将字符串编码为16进制格式,返回字符串
hashCode := hex.EncodeToString(bytes)
//返回哈希值
return hashCode
}
md5加密
func EncodeMd5(data string) string {
h := md5.New()
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
将body中=号格式的字符串转为map
func ConvertToMap(str string) map[string]string {
var resultMap = make(map[string]string)
values := strings.Split(str, "&")
for _, value := range values {
vs := strings.Split(value, "=")
resultMap[vs[0]] = vs[1]
}
return resultMap
}
关于函数的返回
为了避免在灵活性方面受到限制,大多数情况下函数不应该返回接口,而应该返回具体的实现。相反,函数应该尽可能地使用接口作为参数。
nil的slice和map
允许对值为nil
的slice
添加元素,但对值为nil
的map
添加元素,则会造成运行时panic
判断map中key是否存在
- 当访问map中不存在的key时,Go则会返回元素对应数据类型的零值,比如
nil
,false
和0
- 取值操作总有值返回,故不能通过取出来的值,来判断key是不是在map中
- 检查key是否存在可以用map直接访问,检查返回的第二个参数即可
_, ok := x["key"]
string值修改
- 不能,尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的
- string类型的值是只读的二进制byteslice,如果真要修改字符串中的字符
- 将string转为
[]byte
修改后,再转为string即可
解析json数字转成float64
var data = []byte(`{"status": 200}`)
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
log.Fatalln(err)
}
fmt.Printf("%v--%T",result["status"],result["status"]) // 200--float64
解析出来的200是float类型
从panic中恢复
// 错误的 recover 调用示例
func main() {
recover() // 什么都不会捕捉
panic("not good") // 发生 panic,主程序退出
recover() // 不会被执行
println("ok")
}
// 正确的 recover 调用示例
func main() {
defer func() {
fmt.Println("recovered: ", recover())
}()
panic("not good")
}
map的有序遍历
在Go语言中,map是无序的,每次迭代map的顺序可能不同。如果需要按特定顺序遍历map
func main() {
m := map[string]int{
"b": 2,
"a": 1,
"c": 3,
}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
}
sort.Strings
: 函数对切片进行升序排序。sort.Sort(sort.Reverse(sort.StringSlice(keys)))
: 降序排序方式。
Go 使用 interface 时的 7 个常见错误
抽象的目的不是为了含糊不清,而是为了创造一个新的语义层次,在这个层次上,我们可以做到绝对精确。- E.W.Dijkstra
- 接口隔离原则:绝不能强迫客户实现其不使用的接口,也不能强迫客户依赖其不使用的方法。
- 多态性:代码变化会根据接收到的具体数据改变其行为。
- 里氏替换原则:如果你的代码依赖于一个抽象概念,那么一个实现可以被另一个实现所替代,而无需更改你的代码。
- too many interfaces
拥有过多接口的术语叫做接口污染。当你在编写具体类型之前就开始抽象时,就会出现这种情况。由于无法预知需要哪些抽象,因此很容易编写出过多的接口,而这些接口在日后要么是错误的,要么是无用的。
- too many methods
在 PHP 项目中,10 个方法的接口是很常见的。在 Go 中,接口的数量很少,标准库中所有接口的平均方法数量为 2 个。
- 非行为驱动的接口
在传统语言中,诸如 User(用户)、Request(请求)等名词性接口非常常见。而在 Go 语言中,大多数接口都有 er 后缀:Reader
、Writer
、Closer
等。这是因为,在 Go 中,接口暴露了行为,而它们的名称则指向该行为。
- producer 端实现接口
经常在 code review 中看到这种情况:人们在写具体实现的同一个包中定义接口.
- 返回接口
如果一个方法返回的是接口而不是具体的结构,那么所有调用该方法的客户端都会被迫使用相同的抽象。你需要让客户决定他们需要什么样的抽象。
- 粹为测试而创建接口
接口污染的另一个原因是:仅仅因为想模拟一个实现,就创建一个只有一个实现的接口。
- 没有验证接口的兼容性
比方说,你有一个导出名为 User 的类型的软件包,你实现了 Stringer 接口,因为出于某种原因,当你打印时,你不希望显示电子邮件.
字符串高效编码的最佳实践
- Golang中的字符串是不可变的,意味着它们的单个字符不能被改变。
- 重要的是要认识到字符串是字节数组,某些字符可能存储在多个字节中。
- 在确定字符串的长度时,
len
函数返回的是字节数,而不是字符数。要计算字符数,请使用utf8.RuneCountInString()
。 - 字符串索引提供了字符的
uint
值,但要访问rune
值,请使用带有字符串变量的for
循环。 - 在迭代字符串时要小心非UTF-8字符,因为Golang可能会将它们替换为
Unicode
替代字符。 - 为了高效地构建字符串,请优先使用
strings.Builder
或bytes.Buffer
包,而不是使用+
连接方法。
sync.map常用详解
在 Go 中,sync.Map
提供了一种强大且高效的机制来进行并发映射访问。通过利用其并发访问、延迟初始化和动态键值操作等特性,开发者可以在不借助手动同步的情况下,安全地管理跨多个 goroutine
的共享数据结构。将 sync.Map
集成到你的并发 Go 程序中,可以提高性能,并减少管理共享状态的复杂性。
Key Features of sync.Map
- 并发访问:
sync.Map
允许多个goroutine
安全地并发访问映射,而不需要像互斥锁(mutex
)这样的外部同步机制。 - 动态键值对:在
sync.Map
中,键和值可以动态地并发添加、更新或删除。 - 延迟初始化:
sync.Map
不需要初始化,可以在声明后立即使用。 - 无复制:与传统映射不同,
sync.Map
在读写操作期间不会进行复制,这在某些场景下可以提高性能。
Usage of sync.Map
- Example 1: 基本用法
package main
import (
"sync"
)
func main() {
var m sync.Map
// Adding key-value pairs concurrently
m.Store("key1", "value1")
m.Store("key2", "value2")
// Retrieving value for a key
if val, ok := m.Load("key1"); ok {
println(val.(string)) // Output: value1
}
// Deleting a key
m.Delete("key2")
}
- Example 2: 迭代访问
sync.map
package main
import (
"sync"
)
func main() {
var m sync.Map
m.Store("key1", "value1")
m.Store("key2", "value2")
// Iterating over sync.Map
m.Range(func(key, value interface{}) bool {
println(key.(string), value.(string))
return true
})
}
- Example 3: 并发访问
package main
import (
"sync"
)
func main() {
var m sync.Map
var wg sync.WaitGroup
// Concurrent write operations
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
m.Store(n, n*10)
}(i)
}
wg.Wait()
// Concurrent read operations
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
if val, ok := m.Load(n); ok {
println(val.(int))
}
}(i)
}
wg.Wait()
}
omitempty特性
在使用 Go (Golang) 并将数据编码为 JSON 时,您可能会遇到这样的情况:如果某些字段的值为空,则需要从编码输出中省略这些字段。这就是 Golang 的 omitempty
功能发挥作用的地方。struct
字段标记中的 omitempty
选项会告诉编码器,如果字段为空值,则跳过该字段。