一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

巧用Golang泛型,簡(jiǎn)化代碼編寫

OSC開源社區(qū) ? 來(lái)源:百度小程序團(tuán)隊(duì) ? 2023-02-07 16:28 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

導(dǎo)讀introduction 本文整理了很多的泛型應(yīng)用技巧,結(jié)合具體的實(shí)際代碼示例,特別是很多直接對(duì)Go語(yǔ)言內(nèi)置的類庫(kù)的實(shí)現(xiàn)進(jìn)行改造,再通過(guò)兩者在使用上直觀對(duì)比,幫助大家對(duì)泛型使用思考上提供了更多思路,定會(huì)幫助大家在應(yīng)用泛型能力上有很多的提升與啟發(fā)。

GEEK TALK

01

前言

泛型功能是Go語(yǔ)言在1.18版本引入的功能,可以說(shuō)是Go語(yǔ)言開源以來(lái)最大的語(yǔ)法特性變化,其改動(dòng)和影響都很大, 所以整個(gè)版本的開發(fā)周期,測(cè)試周期都比以往要長(zhǎng)很多。接下來(lái)為了大家更好的理解文章中的代碼示例,先再簡(jiǎn)單介紹一下 Go語(yǔ)言在1.18版本加入的泛型的基本使用方法。

從官方的資料來(lái)看,泛型增加了三個(gè)新的重要內(nèi)容:

  1. 函數(shù)和類型新增對(duì)類型形參(type parameters)的支持。

  2. 接口類型定義為類型集合,包括沒(méi)有方法的接口類型。

  3. 支持類型推導(dǎo),大多數(shù)情況下,調(diào)用泛型函數(shù)時(shí)可省略類型實(shí)參(type arguments)。

1.1 Type Parameter

參數(shù)泛型類型(Type Parameter)可以說(shuō)是泛型使用過(guò)程應(yīng)用最多的場(chǎng)景了, 一般應(yīng)用于方法或函數(shù)的形參或返回參數(shù)上。

參數(shù)泛型類型基本的使用格式可參見(jiàn)如下:
funcFuncName[P,Qconstraint1,Rconstraint2,...](parameter1P,parameter2Q,...)(R,Q,...)

說(shuō)明: 參數(shù)泛型類定義后,可以用于函數(shù)的形參或返回參數(shù)上。

下面是一個(gè)應(yīng)用參數(shù)泛型類型的代碼示例:

// Min return the min one
func Min[E int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 | uintptr | ~string](x, y E) E {
    if x < y {
        return x
    }
    return y
}

1.2 類型集合 Type Set

類型集合是為了簡(jiǎn)化泛型約束的使用,提升閱讀性,同時(shí)增加了復(fù)用能力,方式他通過(guò)接口定義的方式使用。

編寫格式參見(jiàn)如下:

type Constraint1 interface {
    Type1 | ~Type2 | ...
}

以下示例定義了有符號(hào)整型與無(wú)符號(hào)整型的泛型約束:

// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}


// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

類型集合也支持繼承的方式,簡(jiǎn)化復(fù)用。使用方式也接口的繼承是完全一致。

以下示例定義把SignedUnsigned進(jìn)行了組合,用于表達(dá)對(duì)整型泛型約束的定義。

// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
    Signed | Unsigned
}

1.3類型推導(dǎo)

引入類型推導(dǎo),可以簡(jiǎn)化我們代碼工作,目前支持2種類型推導(dǎo)功能。

  • 通過(guò)函數(shù)的實(shí)參推導(dǎo)出來(lái)具體的類型以前提到的Min函數(shù)為例,可能通過(guò)傳入的傳數(shù)類型來(lái)判斷推導(dǎo)。

var a, b uint
minUint := Min(a, b) // 不再需要這樣寫 Min[uint](a, b)
fmt.Println(minUint)


minInt := Min(10, 100) // 常量數(shù)字,go語(yǔ)言會(huì)默認(rèn)為 int類型,不再需要這樣寫 Min[int](a, b)
fmt.Println(minInt)

GEEK TALK

02

巧用泛型,實(shí)現(xiàn)通用排序函數(shù)

對(duì)一個(gè)數(shù)組進(jìn)行排序是在業(yè)務(wù)開發(fā)中使用非常頻繁的功能,Go語(yǔ)言提供了sort.Sort函數(shù),提供高效的排序功能支持,但它要求目標(biāo)數(shù)組必須要實(shí)現(xiàn) sort.Interface接口。
// An implementation of Interface can be sorted by the routines in this package.
// The methods refer to elements of the underlying collection by integer index.
type Interface interface {
  // Len is the number of elements in the collection.
  Len() int


  // Less reports whether the element with index i
  // must sort before the element with index j.
  //
  // If both Less(i, j) and Less(j, i) are false,
  // then the elements at index i and j are considered equal.
  // Sort may place equal elements in any order in the final result,
  // while Stable preserves the original input order of equal elements.
  //
  // Less must describe a transitive ordering:
  //  - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
  //  - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
  //
  // Note that floating-point comparison (the < operator on float32 or float64 values)
  // is not a transitive ordering when not-a-number (NaN) values are involved.
  // See Float64Slice.Less for a correct implementation for floating-point values.
  Less(i, j int) bool


  // Swap swaps the elements with indexes i and j.
  Swap(i, j int)
}

這樣導(dǎo)致我們對(duì)不同元素類型的數(shù)組,都需要重復(fù)實(shí)現(xiàn)這個(gè)接口,編寫了很多類似的代碼。下面是官方給的一個(gè)排序的示例,可以看到實(shí)現(xiàn)這樣的排序功能,要寫這么多代碼。

type Person struct {
    Name string
    Age  int
}


// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person


func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }


func main() {
    people := []Person{
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }


    fmt.Println(people)
    // There are two ways to sort a slice. First, one can define
    // a set of methods for the slice type, as with ByAge, and
    // call sort.Sort. In this first example we use that technique.
    sort.Sort(ByAge(people))
    fmt.Println(people)


    // Output:
    // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
    // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
}

下面我們應(yīng)用泛型,編寫一個(gè)通用的排序功能,可以支持任何類型的數(shù)組。大致思路是封裝一個(gè)接受任何類型(any)的結(jié)構(gòu)體sortable,來(lái)實(shí)現(xiàn)sort.Interface接口,把需要擴(kuò)展的部分(比較處理)進(jìn)行可設(shè)置化。主要的代碼如下:

// 通用化的排序?qū)崿F(xiàn)
type sortable[E any] struct {
  data []E
  cmp  base.CMP[E]
}


func (s sortable[E]) Len() int      { return len(s.data) }
func (s sortable[E]) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
func (s sortable[E]) Less(i, j int) bool {
  return s.cmp(s.data[i], s.data[j]) >= 0
}

接入下來(lái),實(shí)現(xiàn)一個(gè)支持泛型的排序函數(shù),對(duì)任何類型的數(shù)組進(jìn)行排序。

func Sort[E any](data []E, cmp base.CMP[E]) {
  sortobject := sortable[E]{data: data, cmp: cmp}
  sort.Sort(sortobject)
}

至此,我們就已經(jīng)實(shí)現(xiàn)一個(gè)通用的排序函數(shù)了, 應(yīng)用這個(gè)函數(shù),上面官方給出的排序?qū)崿F(xiàn)就可以簡(jiǎn)化如下:

type Person struct {
    Name string
    Age  int
}


people := []Person{
    {"Bob", 31},
    {"John", 42},
    {"Michael", 17},
    {"Jenny", 26},
}


people = Sort(people, func(e1, e2 Person) int {
    return e1.Age - e2.Age
})
// Output:
// [Michael: 17 Jenny: 26 Bob: 31 John: 42]

可以看到, 應(yīng)用泛型后,只需要簡(jiǎn)單的一個(gè)函數(shù)調(diào)用就可以了。

完整的代碼實(shí)現(xiàn)可參見(jiàn):https://github.com/jhunters/goassist/blob/main/arrayutil/array.go

GEEK TALK

03

巧用泛型,簡(jiǎn)化strconv.Append系列函數(shù)

Go語(yǔ)言內(nèi)置的strconv包的api也是日常開發(fā)經(jīng)常使用的, 它提供的Append系列函數(shù)可以實(shí)現(xiàn)高效的字符串拼接功能,但因?yàn)镚o語(yǔ)言不支持重載,所以會(huì)看到因?yàn)榻邮軈?shù)類型的不同,需要選擇不同的函數(shù)。

func AppendBool(dst []byte, b bool) []byte
func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte
func AppendInt(dst []byte, i int64, base int) []byte
func AppendQuote(dst []byte, s string) []byte
func AppendQuoteRune(dst []byte, r rune) []byte
func AppendQuoteRuneToASCII(dst []byte, r rune) []byte
func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte
func AppendQuoteToASCII(dst []byte, s string) []byte
func AppendQuoteToGraphic(dst []byte, s string) []byte
func AppendUint(dst []byte, i uint64, base int) []byte

所以我們不得不面臨以下使用的窘境。

// append bool
b := []byte("bool:")
b = strconv.AppendBool(b, true)
fmt.Println(string(b))


// append int
b10 := []byte("int (base 10):")
b10 = strconv.AppendInt(b10, -42, 10)
fmt.Println(string(b10))


// append quote
b := []byte("quote:")
b = strconv.AppendQuote(b, `"Fran & Freddie's Diner"`)
fmt.Println(string(b))

接下來(lái),我們用泛型來(lái)簡(jiǎn)化一下代碼,讓其只需要一個(gè)函數(shù)就能搞定, 直接上代碼如下:

// Append convert e to string and appends to dst
func Append[E any](dst []byte, e E) []byte {
  toAppend := fmt.Sprintf("%v", e)
  return append(dst, []byte(toAppend)...)
}

再來(lái)看看應(yīng)用后的效果,修改之前的示例:

// append bool
b := []byte("bool:")
b = conv.Append(b, true)
fmt.Println(string(b))


// append int
b10 := []byte("int:")
b10 = conv.Append(b10, -42)
fmt.Println(string(b10))


// append quote
b = []byte("quote:")
b = conv.Append(b, `"Fran & Freddie's Diner"`)
fmt.Println(string(b))

GEEK TALK

04

巧用泛型,實(shí)現(xiàn)通用heap容器,簡(jiǎn)化使用

Go語(yǔ)言container/heap包提供了一個(gè)優(yōu)先級(jí)隊(duì)列功能, 以實(shí)現(xiàn)在Pop數(shù)里時(shí),總是優(yōu)先獲得優(yōu)先級(jí)最高的節(jié)點(diǎn)。

同樣的問(wèn)題,如果要應(yīng)用heap包的功能,針對(duì)不同的對(duì)象,必須要 實(shí)現(xiàn) heap.Interface接口, 包括5個(gè)方法。

// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
//  !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.
type Interface interface {
  sort.Interface
  Push(x any) // add x as element Len()
  Pop() any   // remove and return element Len() - 1.
}

下面的代碼示例是來(lái)自Go語(yǔ)言官方,實(shí)現(xiàn)了對(duì)Int類型元素的優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn):

import (
    "container/heap"
    "fmt"
)


// An IntHeap is a min-heap of ints.
type IntHeap []int


func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }


func (h *IntHeap) Push(x any) {
    // Push and Pop use pointer receivers because they modify the slice's length,
    // not just its contents.
    *h = append(*h, x.(int))
}


func (h *IntHeap) Pop() any {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}


// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func Example_intHeap() {
    h := &IntHeap{2, 1, 5}
    heap.Init(h)
    heap.Push(h, 3)
    fmt.Printf("minimum: %d
", (*h)[0])
    for h.Len() > 0 {
        fmt.Printf("%d ", heap.Pop(h))
    }
    // Output:
    // minimum: 1
    // 1 2 3 5
}

看到上面寫了這么多的代碼才把功能實(shí)現(xiàn), 想必大家都覺(jué)得太繁瑣了吧?那我們用泛型來(lái)改造一下,大致思路如下:

  • 實(shí)現(xiàn)一個(gè)支持泛型參數(shù)的結(jié)構(gòu)體heapST,實(shí)現(xiàn)heap.Interface接口。

  • 開放比較函數(shù)的功能,用于使用方來(lái)更靈活的設(shè)置排序要求。

  • 封裝一個(gè)全新的帶泛型參數(shù)傳入Heap結(jié)構(gòu)體, 來(lái)封裝Pop與Push方法的實(shí)現(xiàn)。

主要的代碼實(shí)現(xiàn)如下:

type heapST[E any] struct {
  data []E
  cmp  base.CMP[E]
}


// implments the methods for "heap.Interface"
func (h *heapST[E]) Len() int { return len(h.data) }
func (h *heapST[E]) Less(i, j int) bool {
  v := h.cmp(h.data[i], h.data[j])
  return v < 0
}
func (h *heapST[E]) Swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] }
func (h *heapST[E]) Push(x any) {
  // Push and Pop use pointer receivers because they modify the slice's length,
  // not just its contents.
  v := append(h.data, x.(E))
  h.data = v
}
func (h *heapST[E]) Pop() any {
  old := h.data
  n := len(old)
  x := old[n-1]
  h.data = old[0 : n-1]
  return x
}


// Heap base on generics to build a heap tree for any type
type Heap[E any] struct {
  data *heapST[E]
}


// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
func (h *Heap[E]) Push(v E) {
  heap.Push(h.data, v)
}


// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
func (h *Heap[E]) Pop() E {
  return heap.Pop(h.data).(E)
}


func (h *Heap[E]) Element(index int) (e E, err error) {
  if index < 0 || index >= h.data.Len() {
    return e, fmt.Errorf("out of index")
  }
  return h.data.data[index], nil
}


// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
func (h *Heap[E]) Remove(index int) E {
  return heap.Remove(h.data, index).(E)
}


func (h *Heap[E]) Len() int {
  return len(h.data.data)
}


// Copy to copy heap
func (h *Heap[E]) Copy() *Heap[E] {
  ret := heapST[E]{cmp: h.data.cmp}
  ret.data = make([]E, len(h.data.data))
  copy(ret.data, h.data.data)
  heap.Init(&ret)
  return &Heap[E]{&ret}
}


// NewHeap return Heap pointer and init the heap tree
func NewHeap[E any](t []E, cmp base.CMP[E]) *Heap[E] {
  ret := heapST[E]{data: t, cmp: cmp}
  heap.Init(&ret)
  return &Heap[E]{&ret}
}

完整的代碼獲?。篽ttps://github.com/jhunters/goassist/blob/main/container/heapx/heap.go

接入來(lái)可以改寫之前的代碼, 代碼如下:

// An IntHeap is a min-heap of ints.
type IntHeap []int


// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func Example_intHeap() {
  h := heapx.NewHeap(IntHeap{2, 1, 5}, func(p1, p2 int) int {
    return p1 - p2
  })
  h.Push(3)
  for h.Len() > 0 {
    fmt.Printf("%d ", h.Pop())
  }
  // Output:
  // 1 2 3 5
}

可以看到改寫后,代碼量大量減少,而且代碼的可讀性也大大提升. 完整的使用示例可參見(jiàn):https://github.com/jhunters/goassist/blob/main/container/heapx/heap_test.go

GEEK TALK

05

巧用泛型,提升Pool容器可讀性與安全性

Go語(yǔ)言內(nèi)存的sync包下Pool對(duì)象, 提供了可伸縮、并發(fā)安全的臨時(shí)對(duì)象池的功能,用來(lái)存放已經(jīng)分配但暫時(shí)不用的臨時(shí)對(duì)象,通過(guò)對(duì)象重用機(jī)制,緩解 GC 壓力,提高程序性能。需要注意的是Pool 是一個(gè)臨時(shí)對(duì)象池,適用于儲(chǔ)存一些會(huì)在 goroutine 間共享的臨時(shí)對(duì)象,其中保存的任何項(xiàng)都可能隨時(shí)不做通知地釋放掉,所以不適合當(dāng)于緩存或?qū)ο蟪氐墓δ堋?/span>

Pool的框架代碼如下:

type Pool struct {
  // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
  // It may not be changed concurrently with calls to Get.
  New func() interface{}
  // contains filtered or unexported fields
}


// Get 從 Pool 中獲取元素。當(dāng) Pool 中沒(méi)有元素時(shí),會(huì)調(diào)用 New 生成元素,新元素不會(huì)放入 Pool 中。若 New 未定義,則返回 nil。
func (p *Pool) Get() interface{}


// Put 往 Pool 中添加元素 x。
func (p *Pool) Put(x interface{})



官方Pool的API使用起來(lái)已經(jīng)是非常方便,下面是摘取官方文檔中的示例代碼:

package sync_test


import (
    "bytes"
    "io"
    "os"
    "sync"
    "time"
)


var bufPool = sync.Pool{
    New: func() any {
        // The Pool's New function should generally only return pointer
        // types, since a pointer can be put into the return interface
        // value without an allocation:
        return new(bytes.Buffer)
    },
}


// timeNow is a fake version of time.Now for tests.
func timeNow() time.Time {
    return time.Unix(1136214245, 0)
}


func Log(w io.Writer, key, val string) {
    b := bufPool.Get().(*bytes.Buffer)
    b.Reset()
    // Replace this with time.Now() in a real logger.
    b.WriteString(timeNow().UTC().Format(time.RFC3339))
    b.WriteByte(' ')
    b.WriteString(key)
    b.WriteByte('=')
    b.WriteString(val)
    w.Write(b.Bytes())
    bufPool.Put(b)
}


func ExamplePool() {
    Log(os.Stdout, "path", "/search?q=flowers")
    // Output: 2006-01-02T1505Z path=/search?q=flowers
}

從上面的代碼,可以看到一個(gè)問(wèn)題就是從池中獲取對(duì)象時(shí),要強(qiáng)制進(jìn)行轉(zhuǎn)換,如果轉(zhuǎn)換類型不匹配,就會(huì)出現(xiàn)Panic異常,這種場(chǎng)景正是泛型可以很好解決的場(chǎng)景,我們改造代碼如下, 封裝一個(gè)全新的帶泛型參數(shù)傳入 Pool 結(jié)構(gòu)體:

package syncx


import (
  "sync"


  "github.com/jhunters/goassist/base"
)


type Pool[E any] struct {
  New      base.Supplier[E]
  internal sync.Pool
}


// NewPoolX create a new PoolX
func NewPool[E any](f base.Supplier[E]) *Pool[E] {
  p := Pool[E]{New: f}
  p.internal = sync.Pool{
    New: func() any {
      return p.New()
    },
  }


  return &p
}


// Get selects an E generic type item from the Pool
func (p *Pool[E]) Get() E {
  v := p.internal.Get()
  return v.(E)
}


// Put adds x to the pool.
func (p *Pool[E]) Put(v E) {
  p.internal.Put(v)
}

接下來(lái),使用新封裝的Pool對(duì)象改寫上面的官方示例代碼:


var bufPool = syncx.NewPool(func() *bytes.Buffer {
  return new(bytes.Buffer)
})


// timeNow is a fake version of time.Now for tests.
func timeNow() time.Time {
    return time.Unix(1136214245, 0)
}


func Log(w io.Writer, key, val string) {
    b := bufPool.Get() // 不再需要強(qiáng)制類型轉(zhuǎn)換
    b.Reset()
    // Replace this with time.Now() in a real logger.
    b.WriteString(timeNow().UTC().Format(time.RFC3339))
    b.WriteByte(' ')
    b.WriteString(key)
    b.WriteByte('=')
    b.WriteString(val)
    w.Write(b.Bytes())
    bufPool.Put(b)
}


func ExamplePool() {
    Log(os.Stdout, "path", "/search?q=flowers")
    // Output: 2006-01-02T1505Z path=/search?q=flowers
}

完整的代碼實(shí)現(xiàn)與使用示例可參見(jiàn):https://github.com/jhunters/goassist/tree/main/concurrent/syncx

GEEK TALK

06

巧用泛型,增強(qiáng)sync.Map容器功能

sync.Map是Go語(yǔ)言官方提供的一個(gè)map映射的封裝實(shí)現(xiàn),提供了一些更實(shí)用的方法以更方便的操作map映射,同時(shí)它本身也是線程安全的,包括原子化的更新支持。

type Map
    func (m *Map) Delete(key any)
    func (m *Map) Load(key any) (value any, ok bool)
    func (m *Map) LoadAndDelete(key any) (value any, loaded bool)
    func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)
    func (m *Map) Range(f func(key, value any) bool)
    func (m *Map) Store(key, value any)

接入來(lái)我們要用泛型功能,給sync.Map增加如下功能:

  • 所有的操作支持泛型,以省去對(duì)象強(qiáng)制轉(zhuǎn)換功能

  • 引入泛型后,保障了key與value類型的一致性,可以擴(kuò)展支持 Key或Value是否存在, 查詢最小最大Key或Value的功能

  • 另外還增加了StoreAll 從另一個(gè)map導(dǎo)入, ToMap轉(zhuǎn)成原生map結(jié)構(gòu), Clear清空map, 以數(shù)組結(jié)構(gòu)導(dǎo)出key或value等實(shí)用功能

增加后Map的API列表如下:

type Map
    func NewMap[K comparable, V any]() *Map[K, V]
    func (m *Map[K, V]) Clear()
    func (m *Map[K, V]) Copy() *Map[K, V]
    func (m *Map[K, V]) Exist(key K) bool
    func (m *Map[K, V]) ExistValue(value V) (k K, exist bool)
    func (m *Map[K, V]) ExistValueWithComparator(value V, equal base.EQL[V]) (k K, exist bool)
    func (m *Map[K, V]) Get(key K) (V, bool)
    func (m *Map[K, V]) IsEmpty() (empty bool)
    func (m *Map[K, V]) Keys() []K
    func (m *Map[K, V]) MaxKey(compare base.CMP[K]) (key K, v V)
    func (m *Map[K, V]) MaxValue(compare base.CMP[V]) (key K, v V)
    func (m *Map[K, V]) MinKey(compare base.CMP[K]) (key K, v V)
    func (m *Map[K, V]) MinValue(compare base.CMP[V]) (key K, v V)
    func (m *Map[K, V]) Put(key K, value V) V
    func (m *Map[K, V]) Range(f base.BiFunc[bool, K, V])
    func (m *Map[K, V]) Remove(key K) bool
    func (m *Map[K, V]) Size() int
    func (m *Map[K, V]) ToMap() map[K]V
    func (m *Map[K, V]) Values() []V
完整的API列表在此閱讀:http://localhost:4040/pkg/github.com/jhunters/goassist/container/mapx/

部分泛型代碼后的代碼如下:

// Map is like a Go map[interface{}]interface{} but is safe for concurrent use
// by multiple goroutines without additional locking or coordination.
// Loads, stores, and deletes run in amortized constant time.
// By generics feature supports, all api will be more readable and safty.
//
// The Map type is specialized. Most code should use a plain Go map instead,
// with separate locking or coordination, for better type safety and to make it
// easier to maintain other invariants along with the map content.
//
// The Map type is optimized for two common use cases: (1) when the entry for a given
// key is only ever written once but read many times, as in caches that only grow,
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
// sets of keys. In these two cases, use of a Map may significantly reduce lock
// contention compared to a Go map paired with a separate Mutex or RWMutex.
//
// The zero Map is empty and ready for use. A Map must not be copied after first use.
type Map[K comparable, V any] struct {
  mp    sync.Map
  empty V
  mu    sync.Mutex
}


// NewMap create a new map
func NewMap[K comparable, V any]() *Map[K, V] {
  return &Map[K, V]{mp: sync.Map{}}
}


// NewMapByInitial create a new map and store key and value from origin map
func NewMapByInitial[K comparable, V any](mmp map[K]V) *Map[K, V] {
  mp := NewMap[K, V]()
  if mmp == nil {
    return mp
  }
  for k, v := range mmp {
    mp.Store(k, v)
  }


  return mp
}




// Exist return true if key exist
func (m *Map[K, V]) Exist(key K) bool {
  _, ok := m.mp.Load(key)
  return ok
}


// ExistValue return true if value exist
func (m *Map[K, V]) ExistValue(value V) (k K, exist bool) {
  de := reflectutil.NewDeepEquals(value)
  m.Range(func(key K, val V) bool {
    if de.Matches(val) {
      exist = true
      k = key
      return false
    }
    return true
  })
  return
}


// ExistValue return true if value exist
func (m *Map[K, V]) ExistValueWithComparator(value V, equal base.EQL[V]) (k K, exist bool) {
  m.Range(func(key K, val V) bool {
    if equal(value, val) {
      exist = true
      k = key
      return false
    }
    return true
  })
  return
}


// ExistValue return true if value exist
func (m *Map[K, V]) ExistValueComparable(v base.Comparable[V]) (k K, exist bool) {
  m.Range(func(key K, val V) bool {
    if v.CompareTo(val) == 0 {
      exist = true
      k = key
      return false
    }
    return true
  })
  return
}


// MinValue to return min value in the map
func (m *Map[K, V]) MinValue(compare base.CMP[V]) (key K, v V) {
  return selectByCompareValue(m, func(o1, o2 V) int {
    return compare(o1, o2)
  })


}


// MaxValue to return max value in the map
func (m *Map[K, V]) MaxValue(compare base.CMP[V]) (key K, v V) {
  return selectByCompareValue(m, func(o1, o2 V) int {
    return compare(o2, o1)
  })


}


// MinKey to return min key in the map
func (m *Map[K, V]) MinKey(compare base.CMP[K]) (key K, v V) {
  return selectByCompareKey(m, func(o1, o2 K) int {
    return compare(o1, o2)
  })


}


// MaxKey to return max key in the map
func (m *Map[K, V]) MaxKey(compare base.CMP[K]) (key K, v V) {
  return selectByCompareKey(m, func(o1, o2 K) int {
    return compare(o2, o1)
  })


}
完整的代碼與使用示例參見(jiàn):[1]https://github.com/jhunters/goassist/blob/main/concurrent/syncx/map.go[2]https://github.com/jhunters/goassist/blob/main/concurrent/syncx/example_map_test.go

GEEK TALK

07

總結(jié)

應(yīng)用泛型可以極大的減少代碼的編寫量,同時(shí)提升可讀性與安全性都有幫助,上面提到的對(duì)于泛型的使用技巧只能是很少一部分,還有更多的case等待大家來(lái)發(fā)現(xiàn)。另外對(duì)泛型感興趣的同學(xué),也推薦大家閱讀這個(gè)開源項(xiàng)目 https://github.com/jhunters/goassist 里面有非常多經(jīng)典的泛型使用技巧,相信對(duì)大家理解與掌握泛型會(huì)有很多幫助。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4381

    瀏覽量

    64897
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4900

    瀏覽量

    70756
  • 數(shù)組
    +關(guān)注

    關(guān)注

    1

    文章

    420

    瀏覽量

    26557

原文標(biāo)題:巧用Golang泛型,簡(jiǎn)化代碼編寫

文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    詳解Rust的

    所有的編程語(yǔ)言都致力于將重復(fù)的任務(wù)簡(jiǎn)單化,并為此提供各種各樣的工具。在 Rust 中,(generics)就是這樣一種工具,它是具體類型或其它屬性的抽象替代。在編寫代碼時(shí),我們可以
    發(fā)表于 11-12 09:08 ?1295次閱讀

    Golang的使用

    眾所周知很多語(yǔ)言的function 中都支持 key=word 關(guān)鍵字參數(shù), 但 golang 是不支持的, 我們可以利用去簡(jiǎn)單的實(shí)現(xiàn)。
    發(fā)表于 08-16 12:24 ?431次閱讀

    Java的背景和作用

    Java的背景和作用 Java是Java編程語(yǔ)言中的一個(gè)特性,引入的目的是為了增強(qiáng)
    的頭像 發(fā)表于 09-20 14:30 ?1426次閱讀
    Java<b class='flag-5'>泛</b><b class='flag-5'>型</b>的背景和作用

    Golang接口的作用和應(yīng)用場(chǎng)景

    Golang(Go)作為一門現(xiàn)代的靜態(tài)類型編程語(yǔ)言,提供了許多強(qiáng)大的特性,其中之一便是接口(interface)。接口是Golang中的一個(gè)核心概念,它具有廣泛的應(yīng)用場(chǎng)景,可以幫助開發(fā)者實(shí)現(xiàn)代碼
    的頭像 發(fā)表于 12-05 10:44 ?1431次閱讀

    請(qǐng)問(wèn)怎么在樹莓派上從源代碼構(gòu)建Golang?

    就是指導(dǎo)你如何在樹莓派上從源代碼來(lái)構(gòu)建Go語(yǔ)言。 Google暫時(shí)還沒(méi)有為樹莓派提供預(yù)構(gòu)建好的Golang二進(jìn)制包。盡管在寫作本文時(shí)已經(jīng)有一個(gè)針對(duì)Raspbian系統(tǒng)的Golang包了,但版本較老
    發(fā)表于 05-16 07:55

    labview連接mongdb問(wèn)題,找到不.NET類中的

    有沒(méi)有人用labview連接mongodb數(shù)據(jù)庫(kù)的?已下載mongodb的c#驅(qū)動(dòng),利用labview中的.net控件調(diào)用相關(guān)函數(shù),但是驅(qū)動(dòng)中有部分函數(shù)在類中, labview能調(diào)用c#中的
    發(fā)表于 04-08 13:38

    冒泡排序法的實(shí)現(xiàn)

    冒泡排序法的實(shí)現(xiàn),自用筆記!
    發(fā)表于 01-20 07:22

    型函數(shù)bsearch()的編寫過(guò)程是怎樣的?

    型函數(shù)bsearch()的編寫過(guò)程是怎樣的?
    發(fā)表于 02-09 06:31

    淺析Golang 1.5支持編寫編譯Android原生程序

    已經(jīng)開始支持編寫編譯Android原生程序了(1.4已經(jīng)支持了,1.5連iOS也包括了進(jìn)來(lái),而且和1.4的代碼不兼容,所以我就認(rèn)為它是從1.5開始正式支持)。按捺不住沖動(dòng),一定要試一試,哪怕不能用,至少
    發(fā)表于 07-06 14:16

    編寫高效Lua代碼的方法

    編寫高效Lua代碼的方法
    發(fā)表于 02-07 21:04 ?0次下載

    iOS中關(guān)于的解析

    文章圍繞這五點(diǎn): 1. 是什么 2. 為什么要用 3. 怎么用 4.
    發(fā)表于 09-25 10:01 ?0次下載

    java 編程

    一。 概念的提出(為什么需要)? 首先,我們看下下面這段簡(jiǎn)短的代碼: publicclassGenericTest { public
    發(fā)表于 09-27 11:15 ?0次下載

    Java的工作原理和案例

    是Java語(yǔ)言一個(gè)非常重要的概念,在Java集合類框架中被廣泛應(yīng)用。在介紹之前先看一個(gè)例子。
    的頭像 發(fā)表于 07-01 10:14 ?2901次閱讀

    GoLang的安裝和使用

    GoLang的安裝和使用
    的頭像 發(fā)表于 01-13 14:06 ?1527次閱讀
    <b class='flag-5'>GoLang</b>的安裝和使用

    深入了解Java——從前世今生到PECS原則

    元素?”的問(wèn)題,也會(huì)出現(xiàn)感嘆Java的限制太多了很難用的情況。 為了更好的使用,就需要更深地了解它,因此本文主要介紹
    的頭像 發(fā)表于 11-21 11:45 ?541次閱讀
    深入了解Java<b class='flag-5'>泛</b><b class='flag-5'>型</b>——從前世今生到PECS原則