#45 Golang 自动过期 Map

2021-04-26
type ExpiringMap struct {
    m      sync.Map
    expiry time.Duration
}

func (e *ExpiringMap) set(key, value interface{}) {
    e.m.Store(key, value)

    go func() {
        time.Sleep(e.expiry)
        e.m.Delete(key)
    }()
}

改进:定时清理

package main

import (
    "sync"
    "time"
)

type Item struct {
    value      interface{}
    expiration int64
}

type ExpiringMap struct {
    m       sync.Mutex
    items   map[string]Item
    timeout time.Duration
}

func NewExpiringMap(timeout time.Duration) *ExpiringMap {
    em := &ExpiringMap{
        items:   make(map[string]Item),
        timeout: timeout,
    }
    go em.cleanup()
    return em
}

func (em *ExpiringMap) Set(key string, value interface{}) {
    em.m.Lock()
    defer em.m.Unlock()
    em.items[key] = Item{
        value:      value,
        expiration: time.Now().Add(em.timeout).UnixNano(),
    }
}

func (em *ExpiringMap) Get(key string) (interface{}, bool) {
    em.m.Lock()
    defer em.m.Unlock()
    item, found := em.items[key]
    if !found {
        return nil, false
    }
    if time.Now().UnixNano() > item.expiration {
        return nil, false
    }
    return item.value, true
}

func (em *ExpiringMap) Delete(key string) {
    em.m.Lock()
    defer em.m.Unlock()
    delete(em.items, key)
}

func (em *ExpiringMap) deleteExpiredKeys() {
    em.m.Lock()
    defer em.m.Unlock()
    now := time.Now().UnixNano()
    for key, item := range em.items {
        if now < item.expiration {
            break
        }
        delete(em.items, key)
    }
}

func (em *ExpiringMap) cleanup() {
    for {
        time.Sleep(em.timeout) // 阻塞协程
        em.deleteExpiredKeys()
    }
}

func main() {
    em := NewExpiringMap(time.Second * 10)

    time.Sleep(time.Second * 15)

    _, ok := em.Get("key1")
    if !ok {
        println("key1 is deleted")
    }

    _, ok = em.Get("key2")
    if !ok {
        println("key2 is deleted")
    }
}

#44 fasthttp: 十倍速

2021-04-26

https://github.com/valyala/fasthttp

fasthttp

package main

import (
    "flag"
    "fmt"
    "log"

    "github.com/valyala/fasthttp"
)

var (
    addr     = flag.String("addr", ":8080", "TCP address to listen to")
    compress = flag.Bool("compress", false, "Whether to enable transparent response compression")
)

func main() {
    flag.Parse()

    h := requestHandler
    if *compress {
        h = fasthttp.CompressHandler(h)
    }

    if err := fasthttp.ListenAndServe(*addr, h); err != nil {
        log.Fatalf("Error in ListenAndServe: %v", err)
    }
}

func requestHandler(ctx *fasthttp.RequestCtx) {
    fmt.Fprintf(ctx, "Hello, world!\n\n")

    fmt.Fprintf(ctx, "Request method is %q\n", ctx.Method())
    fmt.Fprintf(ctx, "RequestURI is %q\n", ctx.RequestURI())
    fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path())
    fmt.Fprintf(ctx, "Host is %q\n", ctx.Host())
    fmt.Fprintf(ctx, "Query string is %q\n", ctx.QueryArgs())
    fmt.Fprintf(ctx, "User-Agent is %q\n", ctx.UserAgent())
    fmt.Fprintf(ctx, "Connection has been established at %s\n", ctx.ConnTime())
    fmt.Fprintf(ctx, "Request has been started at %s\n", ctx.Time())
    fmt.Fprintf(ctx, "Serial request number for the current connection is %d\n", ctx.ConnRequestNum())
    fmt.Fprintf(ctx, "Your ip is %q\n\n", ctx.RemoteIP())

    fmt.Fprintf(ctx, "Raw request is:\n---CUT---\n%s\n---CUT---", &ctx.Request)

    ctx.SetContentType("text/plain; charset=utf8")

    // Set arbitrary headers
    ctx.Response.Header.Set("X-My-Header", "my-header-value")

    // Set cookies
    var c fasthttp.Cookie
    c.SetKey("cookie-name")
    c.SetValue("cookie-value")
    ctx.Response.Header.SetCookie(&c)
}
  • r.Body -> ctx.PostBody()
  • r.URL.Path -> ctx.Path()
  • r.URL -> ctx.URI()
  • r.Method -> ctx.Method()
  • r.Header -> ctx.Request.Header
  • r.Header.Get() -> ctx.Request.Header.Peek()
  • r.Host -> ctx.Host()
  • r.Form -> ctx.QueryArgs() + ctx.PostArgs()
  • r.PostForm -> ctx.PostArgs()
  • r.FormValue() -> ctx.FormValue()
  • r.FormFile() -> ctx.FormFile()
  • r.MultipartForm -> ctx.MultipartForm()
  • r.RemoteAddr -> ctx.RemoteAddr()
  • r.RequestURI -> ctx.RequestURI()
  • r.TLS -> ctx.IsTLS()
  • r.Cookie() -> ctx.Request.Header.Cookie()
  • r.Referer() -> ctx.Referer()
  • r.UserAgent() -> ctx.UserAgent()
  • w.Header() -> ctx.Response.Header
  • w.Header().Set() -> ctx.Response.Header.Set()
  • w.Header().Set("Content-Type") -> ctx.SetContentType()
  • w.Header().Set("Set-Cookie") -> ctx.Response.Header.SetCookie()
  • w.Write() -> ctx.Write(), ctx.SetBody(), ctx.SetBodyStream(), ctx.SetBodyStreamWriter()
  • w.WriteHeader() -> ctx.SetStatusCode()
  • w.(http.Hijacker).Hijack() -> ctx.Hijack()
  • http.Error() -> ctx.Error()
  • http.FileServer() -> fasthttp.FSHandler(), fasthttp.FS
  • http.ServeFile() -> fasthttp.ServeFile()
  • http.Redirect() -> ctx.Redirect()
  • http.NotFound() -> ctx.NotFound()
  • http.StripPrefix() -> fasthttp.PathRewriteFunc

fasthttp/router

  • /user/{user} named
  • /user/{user}_admin
  • /user/{user?} optional
  • /user/{name:[a-zA-Z]{5}} regex
  • /user/{name?:[a-zA-Z]{5}}
  • /user/{name:*} cache all

fasthttp/session

package main

import (
    "log"
    "time"

    "github.com/fasthttp/session/v2"
    "github.com/fasthttp/session/v2/providers/redis"
)

var serverSession *session.Session

func init() {
    var provider session.Provider
    var err error
    encoder := session.MSGPEncode
    decoder := session.MSGPDecode
    provider, err = redis.New(redis.Config{
        KeyPrefix:   "session",
        Addr:        "127.0.0.1:6379",
        PoolSize:    8,
        IdleTimeout: 30 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }
    cfg := session.NewDefaultConfig()
    cfg.EncodeFunc = encoder
    cfg.DecodeFunc = decoder
    serverSession = session.New(cfg)
    if err = serverSession.SetProvider(provider); err != nil {
        log.Fatal(err)
    }
}
// 获取 SessionStore
store, err := serverSession.Get(ctx)
if err != nil {
    ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
    return
}
defer func() {
    if err := serverSession.Save(ctx, store); err != nil {
        ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
    }
}()

// Set
store.Set("foo", "bar")

// Get
val := store.Get("foo")
if val == nil {
    // ...
}

store.Delete(key)

store.Set(bytesKey)
val := store.GetBytes(bytesKey)
store.DeleteBytes(bytesKey)

data := store.GetAll()  // Dict
data := store.Ptr()     // *Dict

store.Flush()
store.Reset()

// Session ID,默认 sessionid
id := store.GetSessionID()
store.SetSessionID(bytesID)

// Session 有效期
changed := store.HasExpirationChanged()
duration := store.GetExpiration()
err := store.SetExpiration(duration)

savsgio/atreugo

构建与 fasthttp 和 fasthttp/router 之上的 HTTP 框架,做了一些简单的封装工作。
提供了几个示例

基础用法:

package main

import (
    "github.com/savsgio/atreugo/v11"
)

func main() {
    config := atreugo.Config{
        Addr: "0.0.0.0:8000",
    }
    server := atreugo.New(config)

    server.GET("/", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Hello World")
    })

    server.GET("/echo/{path:*}", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Echo message: " + ctx.UserValue("path").(string))
    })

    v1 := server.NewGroupPath("/v1")
    v1.GET("/", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Hello V1 Group")
    })

    if err := server.ListenAndServe(); err != nil {
        panic(err)
    }
}

#43 Golang: possible resource leak,'defer' is called in the 'for' loop

2021-04-01

类似代码:

for _, f := range files {
    fp, err := os.Open(f)
    if err != nil {
        panic(err)
    }
    defer fp.Close()
    // do something
}

Goland 提示:possible resource leak,'defer' is called in the 'for' loop

defer 是在最后函数退出时执行,但是变量已经经过循环覆盖,可能会导致内存泄漏。
似乎 Golang 的 defer 还是不够聪明。

换种写法:

for _, f := range files {
    func () {
        fp, err := os.Open(f)
        if err != nil {
            panic(err)
        }
        defer fp.Close()
        // do something
    }()
}

#42 Golang ORM 框架

2021-03-30
  • GORM
  • ent
  • Xorm 转到Gitea
  • Bun
  • Reform
  • GoRose

GitHub 搜索结果

  1. go-gorm/gorm shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    The fantastic ORM library for Golang, aims to be developer friendly
  2. ent/ent shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    An entity framework for Go
  3. geektutu/7days-golang shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    7 days golang programs from scratch (web framework Gee, distributed cache GeeCache, object relational mapping ORM framework GeeORM, rpc framework GeeRPC etc) 7 天用 Go 动手写/从零实现系列
  4. gogf/gf shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    GoFrame is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
  5. sqlc-dev/sqlc shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Generate type-safe code from SQL
  6. go-xorm/xorm shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Simple and Powerful ORM for Go, support mysql,postgres,tidb,sqlite3,mssql,oracle, Moved to https://gitea.com/xorm/xorm
  7. volatiletech/sqlboiler shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Generate a Go ORM tailored to your database schema.
  8. go-pg/pg shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Golang ORM with focus on PostgreSQL features and performance
  9. go-gorp/gorp shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Go Relational Persistence - an ORM-ish library for Go
  10. xo/xo shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Command line tool to generate idiomatic Go code for SQL databases supporting PostgreSQL, MySQL, SQLite, Oracle, and Microsoft SQL Server
  11. upper/db shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Data access layer for PostgreSQL, CockroachDB, MySQL, SQLite and MongoDB with ORM-like features.
  12. uptrace/bun shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    SQL-first Golang ORM
  13. xxjwxc/gormt shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    database to golang struct
  14. steebchen/prisma-client-go shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Prisma Client Go is an auto-generated and fully type-safe database client
  15. xormplus/xorm shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    xorm 是一个简单而强大的 Go 语言 ORM 库,通过它可以使数据库操作非常简便。本库是基于原版 xorm 的定制增强版本,为 xorm 提供类似 ibatis 的配置文件及动态 SQL 支持,支持 AcitveRecord 操作
  16. go-reform/reform shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A better ORM for Go, based on non-empty interfaces and code generation.
  17. gobuffalo/pop shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A Tasty Treat For All Your Database Needs
  18. bobohume/gonet shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    go 分布式服务器,基于内存 mmo
  19. unionj-cloud/go-doudou shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    go-doudou(doudou pronounce /dəudəu/)is OpenAPI 3.0 (for REST) spec and Protobuf v3 (for grpc) based lightweight microservice framework. It supports monolith service application as well.
  20. gohouse/gorose shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    GoRose(go orm), a mini database ORM for golang, which inspired by the famous php framwork laravle's eloquent. It will be friendly for php developer and python or ruby developer. Currently provides six major database drivers: mysql,sqlite3,postgres,oracle,mssql, Clickhouse.
  21. huandu/go-sqlbuilder shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A flexible and powerful SQL string builder library plus a zero-config ORM.

#41 Golang 入门项目 httpbin 总结

2021-03-29

httpbin 是我练手的一个非常简单的小项目,功能就是:
1. HTTP POST 请求 (POST /) 提交一个字符串,服务器返回一个 ID。
1. HTTP GET 请求 (GET /xxxx),返回 ID 对应的字符串。

#40 Golang compress

2021-03-01
  • archive: 打包 (tar, zip)
    参考:《Golang archive
  • compress: 压缩 (bzip2, flate, gzip, lzw, zlib)

压缩 & 解压缩

import (
    "bytes"
    "compress/gzip"
    "io"
)

func compressData(data string) ([]byte, error) {
    var buf bytes.Buffer
    gz := gzip.NewWriter(&buf)
    _, err := gz.Write([]byte(data))
    if err != nil {
        return nil, err
    }
    if err := gz.Close(); err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

func decompressData(compressedData []byte) (string, error) {
    r, err := gzip.NewReader(bytes.NewReader(compressedData))
    if err != nil {
        return "", err
    }
    defer r.Close()
    decompressedData, err := io.ReadAll(r)
    if err != nil {
        return "", err
    }
    return string(decompressedData), nil
}

压缩 & 解压缩 & 列出压缩包中的内容

import (
    "archive/zip"
)

// 压缩文件
func compressFile(zipFilename string, filenames ...string) error {
    zipFile, err := os.Create(zipFilename)
    if err != nil {
        return err
    }
    defer zipFile.Close()

    zipWriter := zip.NewWriter(zipFile)
    defer zipWriter.Close()

    for _, filename := range filenames {
        file, err := os.Open(filename)
        if err != nil {
            return err
        }
        defer file.Close()
        fi, err := file.Stat()
        if err != nil {
            return err
        }
        fileHeader, err := zip.FileInfoHeader(fi)
        if err != nil {
            return err
        }

        // 设置文件名(压缩后的文件名)
        fileHeader.Name = filename

        // 写入文件头部
        writer, err := zipWriter.CreateHeader(fileHeader)
        if err != nil {
            return err
        }

        // 复制文件内容到压缩包中
        _, err = io.Copy(writer, file)
        if err != nil {
            return err
        }
    }

    return nil
}

// 解压缩文件
func decompressFile(zipFilename, targetDir string) error {
    zipReader, err := zip.OpenReader(zipFilename)
    if err != nil {
        return err
    }
    defer zipReader.Close()

    for _, file := range zipReader.File {
        // 打开压缩包中的文件
        fileReader, err := file.Open()
        if err != nil {
            return err
        }
        defer fileReader.Close()

        // 创建目标文件
        extractedFilePath := fmt.Sprintf("%s/%s", targetDir, file.Name)
        extractedFile, err := os.Create(extractedFilePath)
        if err != nil {
            return err
        }
        defer extractedFile.Close()

        // 复制文件内容到目标文件
        _, err = io.Copy(extractedFile, fileReader)
        if err != nil {
            return err
        }
    }

    return nil
}

func listFilesInZip(zipFilename string) ([]string, error) {
    files := []string{}

    zipReader, err := zip.OpenReader(zipFilename)
    if err != nil {
        return files, err
    }
    defer zipReader.Close()

    for _, file := range zipReader.File {
        files = append(files, file.Name)
    }
    return files, nil
}

#39 Golang archive

2021-03-01

标准库中的 archive 支持 tarzip 两种打包格式。

https://en.wikipedia.org/wiki/Tar_(computing)

最常用的 tar 包:

func createTarArchive(sourceDir, targetFile string) error {
    file, err := os.Create(targetFile)
    if err != nil {
        return err
    }
    defer file.Close()

    tarWriter := tar.NewWriter(file)
    defer tarWriter.Close()

    return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        header, err := tar.FileInfoHeader(info, info.Name())
        if err != nil {
            return err
        }

        if err := tarWriter.WriteHeader(header); err != nil {
            return err
        }

        if !info.IsDir() {
            file, err := os.Open(path)
            if err != nil {
                return err
            }
            defer file.Close()

            _, err = io.Copy(tarWriter, file)
            if err != nil {
                return err
            }
        }

        return nil
    })
}

func extractTarArchive(sourceFile, targetDir string) error {
    file, err := os.Open(sourceFile)
    if err != nil {
        return err
    }
    defer file.Close()

    tarReader := tar.NewReader(file)
    for {
        header, err := tarReader.Next()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        targetPath := filepath.Join(targetDir, header.Name)
        if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
            return err
        }
        if header.Typeflag == tar.TypeReg {
            file, err := os.Create(targetPath)
            if err != nil {
                return err
            }
            defer file.Close()
            _, err = io.Copy(file, tarReader)
            if err != nil {
                return err
            }
        }
    }

    return nil
}

#38 Golang 数组 & 切片

2021-02-24

数组声明时可以使用 ... 当作长度,表示自动判断。
数组声明时,可以只初始化其中的部分值。

a := [...]int{2: 1, 4: 2} // [5]int{0, 0, 1, 0, 4}

b := [5]int{1, 2, 3} // [5]int{1, 2, 3, 0, 0}

基本操作

a := [...]int{1, 2, 3}
var b []int

// 新增
b = append(b, 1)
// 删除元素
s = append(s[:index], s[index+1:]...)

// 遍历
for i := range a {
    fmt.Println(a[i])
}
for index, value := range a {
    fmt.Printf("%v: %v", index, value)
}
for _, value := range a {
    fmt.Printf("%v", value)
}
for i := 0; i < len(array); i++ {
    fmt.Printf("value: %d\n", array[i])
}

// 判断元素是否存在/获取元素序号
func Index(target int, array []int) int {
    for i, v := range array {
        if target == v {
            return i
        }
    }
    return -1
}

示例

package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := [5]int{1, 3, 5, 7, 9}
    fmt.Printf("%s\t%#v\n", reflect.TypeOf(a), a)
    // [5]int   [5]int{1, 3, 5, 7, 9}

    // array => slice
    b := a[:] // a[0:len(a)]
    fmt.Printf("%s\t%#v\n", reflect.TypeOf(b), b)
    // []int    []int{1, 3, 5, 7, 9}

    // slice => array
    // c := ([5]int)(b) // cannot convert b (type []int) to type [5]int
    c := (*[5]int)(b) // 切片只能转换成数组指针
    fmt.Printf("%s\t%#v\n", reflect.TypeOf(c), c)
    // *[5]int  &[5]int{1, 3, 5, 7, 9}

    // 用类型别名试试:
    type NumArr [5]int
    c2 := (*NumArr)(b)
    fmt.Printf("%s\t%#v\n", reflect.TypeOf(c2), c2)
    // *main.NumArr &main.NumArr{1, 3, 5, 7, 9}

    // 只能遍历赋值
    d := [5]int{}
    for index, v := range b {
        d[index] = v
    }
    fmt.Printf("%s\t%#v\n", reflect.TypeOf(d), d)

    // 通过 copy 的方法实现 slice2array
    e := [5]int{}
    copy(e[:], b) // return length of b
    fmt.Printf("%s\t%#v\n", reflect.TypeOf(e), e)
}

去重

func Unique(arr []int) []int {
    arrLen := len(arr) - 1

    for arrLen > 0 {
        for i := arrLen - 1; i >= 0; i-- {
            if arr[arrLen] == arr[i] {
                arr = append(arr[:i], arr[i+1:]...)
                break
            }
        }
        arrLen--
    }

    return arr
}

func UniqueOptimized(arr []int) []int {
    uniqueArr := make([]int, 0, len(arr))
    uniqueMap := make(map[int]struct{})

    for _, num := range arr {
        if _, ok := uniqueMap[num]; !ok {
            uniqueArr = append(uniqueArr, num)
            uniqueMap[num] = struct{}{}
        }
    }

    return uniqueArr
}

// BenchmarkUniqueOriginal-20           135           8658964 ns/op               0 B/op          0 allocs/op
// BenchmarkUniqueOptimized-20         3501            347027 ns/op          285402 B/op        208 allocs/op

可以看到,一个性能好一些(只用 4% 的时间),资源使用多一些。

使用反射

https://blog.csdn.net/youngwhz1/article/details/83026263

func SliceRemoveDuplicate(a interface{}) (ret []interface{}) {
    if reflect.TypeOf(a).Kind() != reflect.Slice {
        fmt.Printf("<SliceRemoveDuplicate> <a> is not slice but %T\n", a)
        return ret
    }
    va := reflect.ValueOf(a)
    for i := 0; i < va.Len(); i++ {
        if i > 0 && reflect.DeepEqual(va.Index(i-1).Interface(), va.Index(i).Interface()) {
            continue
        }
        ret = append(ret, va.Index(i).Interface())
    }
    return ret
}

func SliceInsert(s []interface{}, index int, value interface{}) []interface{} {
    rear := append([]interface{}{}, s[index:]...)
    return append(append(s[:index], value), rear...)
}

func SliceInsert2(s *[]interface{}, index int, value interface{}) {
    rear := append([]interface{}{}, (*s)[index:]...)
    *s = append(append((*s)[:index], value), rear...)
}

func SliceInsert3(s interface{}, index int, value interface{}) bool {
    if ps, ok := s.(*[]string); ok {
        if val, ok := value.(string); ok {
            rear := append([]string{}, (*ps)[index:]...)
            *ps = append(append((*ps)[:index], val), rear...)
            return true
        }
    } else if ps, ok := s.(*[]int); ok {
        if val, ok := value.(int); ok {
            rear := append([]int{}, (*ps)[index:]...)
            *ps = append(append((*ps)[:index], val), rear...)
        }
    } else if ps, ok := s.(*[]float64); ok {
        if val, ok := value.(float64); ok {
            rear := append([]float64{}, (*ps)[index:]...)
            *ps = append(append((*ps)[:index], val), rear...)
        }
    } else {
        fmt.Printf("<SliceInsert3> Unsupported type: %T\n", s)
    }
    return false
}

func SliceRemove(s []interface{}, index int) []interface{} {
    return append(s[:index], s[index+1:]...)
}

func SliceRemove2(s *[]interface{}, index int) {
    *s = append((*s)[:index], (*s)[index+1:]...)
}

func SliceRemove3(s interface{}, index int) bool {
    if ps, ok := s.(*[]string); ok {
        *ps = append((*ps)[:index], (*ps)[index+1:]...)
    } else if ps, ok := s.(*[]int); ok {
        *ps = append((*ps)[:index], (*ps)[index+1:]...)
    } else if ps, ok := s.(*[]float64); ok {
        *ps = append((*ps)[:index], (*ps)[index+1:]...)
    } else {
        fmt.Printf("<SliceRemove3> Unsupported type: %T\n", s)
        return false
    }
    return true
}

func SliceClear(s *[]interface{}) {
    *s = append([]interface{}{})
}

func SliceClear2(s *[]interface{}) {
    *s = (*s)[0:0]
}

func SliceClear3(s interface{}) bool {
    if ps, ok := s.(*[]string); ok {
        *ps = (*ps)[0:0]
        //*ps = append([]string{})
    } else if ps, ok := s.(*[]int); ok {
        *ps = (*ps)[0:0]
        //*ps = append([]int{})
    } else if ps, ok := s.(*[]float64); ok {
        *ps = (*ps)[0:0]
        //*ps = append([]float64{})
    } else {
        fmt.Printf("<SliceClear3> Unsupported type: %T\n", s)
        return false
    }
    return true
}

泛型实现

func Insert[S ~[]E, E any](s S, i int, v ...E) S {
    tot := len(s) + len(v)
    if tot <= cap(s) {
        s2 := s[:tot]
        copy(s2[i+len(v):], s[i:])
        copy(s2[i:], v)
        return s2
    }
    s2 := make(S, tot)
    copy(s2, s[:i])
    copy(s2[i:], v)
    copy(s2[i+len(v):], s[i:])
    return s2
}

内置方法 make

make(Type, Len, Cap) 可以用来为 slice, map, channel 三种类型初始化(类似:C 语言 (int *)malloc(5))。

这里就看 Slice 的情况。

s1 := make([]int, 5)

s2 := make([]int, 5, 10)
// s2[8] = 1 // panic: runtime error: index out of range [8] with length 5
s2 = append(s2, 1)

如果有预留空间,append 的时候可以不用重新分配内存并遍历赋值。
如果切片有扩容的需要,就最好采用 make 来初始化。

如果不指定容量,则切片的容量和长度将相等。

#37 Golang: 临时文件

2021-02-22
  1. 创建临时文件
  2. 使用编辑器编辑
  3. 获取内容
  4. 删除临时文件
package main

import (
    "bufio"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "os/exec"
)

func main() {
    // create a tmp file
    // ioutil.TempFile creates a temp file and opens the file for reading and writing
    // and returns the resulting *os.File (file descriptor).
    tmpFile, err := ioutil.TempFile(os.TempDir(), "todo-tmp-")
    if err != nil {
        log.Fatal("Cannot create temporary file", err)
    }
    // defer os.Remove(tmpFile.Name())
    defer tmpFile.Close()
    fmt.Println("Created File: " + tmpFile.Name())

    // choose editor by env var
    editor := os.Getenv("EDITOR")
    args := []string{}
    if editor == "" {
        editor = "vim"
        args = append(args, "--clean")
        fmt.Println("No EDITOR enviroment variable set, use " + editor)
    } else {
        fmt.Println("Using EDITOR enviroment variable: " + editor)
    }
    args = append(args, tmpFile.Name())
    fmt.Println("Opening file with " + editor + " with args: " + fmt.Sprint(args))

    // check the editor command is available
    // _, err = exec.LookPath(editor)
    // if err != nil {
    //  log.Fatal("Cannot find editor: " + editor)
    // }

    // call the editor
    cmd := exec.Command(editor, args...)
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err = cmd.Run()
    if err != nil {
        log.Fatal("Cannot run editor: " + editor)
    }

    // read the file
    // data, err := ioutil.ReadFile(tmpFile.Name())
    // if err != nil {
    //  log.Fatal("Cannot read file: " + tmpFile.Name())
    // }
    tmpFile.Seek(0, 0)
    s := bufio.NewScanner(tmpFile)
    for s.Scan() {
        fmt.Println("Content:\n\n" + s.Text())
    }
    if err = s.Err(); err != nil {
        log.Fatal("error reading temp file", err)
    }
}

#36 Golang strings

2021-02-21
func Clone(s string) string                 //
func Compare(a, b string) int               // 比较
func Contains(s, substr string) bool        // 包含子字符串
func ContainsAny(s, chars string) bool      // 包含任意Char
func ContainsRune(s string, r rune) bool    // 包含Rune
func Count(s, substr string) int            // 计数
func Cut(s, sep string) (before, after string, found bool)
func EqualFold(s, t string) bool                    //
func Fields(s string) []string                      // 切割
func FieldsFunc(s string, f func(rune) bool) []string
func HasPrefix(s, prefix string) bool               // 前缀判断 startswith
func HasSuffix(s, suffix string) bool               // 后缀判断 endswith
func Index(s, substr string) int                    // 查找
func IndexAny(s, chars string) int                  // 查找Any
func IndexByte(s string, c byte) int                // 查找Byte
func IndexFunc(s string, f func(rune) bool) int     // 查找Func
func IndexRune(s string, r rune) int                // 查找Rune
func Join(elems []string, sep string) string        // 连接
func LastIndex(s, substr string) int                // 右查找
func LastIndexAny(s, chars string) int              // 右查找Any
func LastIndexByte(s string, c byte) int            // 右查找Byte
func LastIndexFunc(s string, f func(rune) bool) int // 右查找Func
func Map(mapping func(rune) rune, s string) string  //
func Repeat(s string, count int) string             // 重复
func Replace(s, old, new string, n int) string      // 替换
func ReplaceAll(s, old, new string) string          // 替换 = Replace -1
func Split(s, sep string) []string                  // 切割
func SplitAfter(s, sep string) []string             //
func SplitAfterN(s, sep string, n int) []string     //
func SplitN(s, sep string, n int) []string          //
func Title(s string) string                         //
func ToLower(s string) string                       // 转小写
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToTitle(s string) string                       // Title
func ToTitleSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string
func ToValidUTF8(s, replacement string) string
func Trim(s, cutset string) string
func TrimFunc(s string, f func(rune) bool) string
func TrimLeft(s, cutset string) string
func TrimLeftFunc(s string, f func(rune) bool) string
func TrimPrefix(s, prefix string) string
func TrimRight(s, cutset string) string
func TrimRightFunc(s string, f func(rune) bool) string
func TrimSpace(s string) string
func TrimSuffix(s, suffix string) string

type Builder
    func (b *Builder) Cap() int
    func (b *Builder) Grow(n int)
    func (b *Builder) Len() int
    func (b *Builder) Reset()
    func (b *Builder) String() string
    func (b *Builder) Write(p []byte) (int, error)
    func (b *Builder) WriteByte(c byte) error
    func (b *Builder) WriteRune(r rune) (int, error)
    func (b *Builder) WriteString(s string) (int, error)

type Reader
    func NewReader(s string) *Reader
    func (r *Reader) Len() int
    func (r *Reader) Read(b []byte) (n int, err error)
    func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
    func (r *Reader) ReadByte() (byte, error)
    func (r *Reader) ReadRune() (ch rune, size int, err error)
    func (r *Reader) Reset(s string)
    func (r *Reader) Seek(offset int64, whence int) (int64, error)
    func (r *Reader) Size() int64
    func (r *Reader) UnreadByte() error
    func (r *Reader) UnreadRune() error
    func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

type Replacer
    func NewReplacer(oldnew ...string) *Replacer
    func (r *Replacer) Replace(s string) string
    func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)

Builder

Reader

Replacer

参考资料与拓展阅读