[持续更新]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

允许对值为nilslice添加元素,但对值为nilmap添加元素,则会造成运行时panic

判断map中key是否存在

  • 当访问map中不存在的key时,Go则会返回元素对应数据类型的零值,比如nilfalse0
  • 取值操作总有值返回,故不能通过取出来的值,来判断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

  • 接口隔离原则:绝不能强迫客户实现其不使用的接口,也不能强迫客户依赖其不使用的方法。
  • 多态性:代码变化会根据接收到的具体数据改变其行为。
  • 里氏替换原则:如果你的代码依赖于一个抽象概念,那么一个实现可以被另一个实现所替代,而无需更改你的代码。
  1. too many interfaces

拥有过多接口的术语叫做接口污染。当你在编写具体类型之前就开始抽象时,就会出现这种情况。由于无法预知需要哪些抽象,因此很容易编写出过多的接口,而这些接口在日后要么是错误的,要么是无用的。

  1. too many methods

在 PHP 项目中,10 个方法的接口是很常见的。在 Go 中,接口的数量很少,标准库中所有接口的平均方法数量为 2 个。

  1. 非行为驱动的接口

在传统语言中,诸如 User(用户)、Request(请求)等名词性接口非常常见。而在 Go 语言中,大多数接口都有 er 后缀:ReaderWriterCloser 等。这是因为,在 Go 中,接口暴露了行为,而它们的名称则指向该行为。

  1. producer 端实现接口

经常在 code review 中看到这种情况:人们在写具体实现的同一个包中定义接口.

  1. 返回接口

如果一个方法返回的是接口而不是具体的结构,那么所有调用该方法的客户端都会被迫使用相同的抽象。你需要让客户决定他们需要什么样的抽象。

  1. 粹为测试而创建接口

接口污染的另一个原因是:仅仅因为想模拟一个实现,就创建一个只有一个实现的接口。

  1. 没有验证接口的兼容性

比方说,你有一个导出名为 User 的类型的软件包,你实现了 Stringer 接口,因为出于某种原因,当你打印时,你不希望显示电子邮件.

字符串高效编码的最佳实践

  • Golang中的字符串是不可变的,意味着它们的单个字符不能被改变。
  • 重要的是要认识到字符串是字节数组,某些字符可能存储在多个字节中。
  • 在确定字符串的长度时,len 函数返回的是字节数,而不是字符数。要计算字符数,请使用 utf8.RuneCountInString()
  • 字符串索引提供了字符的 uint 值,但要访问 rune 值,请使用带有字符串变量的 for 循环。
  • 在迭代字符串时要小心非UTF-8字符,因为Golang可能会将它们替换为Unicode替代字符。
  • 为了高效地构建字符串,请优先使用 strings.Builderbytes.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

  1. 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")
}
  1. 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
    })
}
  1. 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 选项会告诉编码器,如果字段为空值,则跳过该字段。

关于我
loading