zoukankan      html  css  js  c++  java
  • Golang高效实践之array、slice、map实践

    前言

    Golang的slice类型为连续同类型数据提供了一个方便并且高效的实现方式。slice的实现是基于array,slice和map一样是类似于指针语义,传递slice和map并不涉及底层数据结构的拷贝,相当于传递底层数据结构的指针。

    Arrays数组 

    数组类型的定义需要指定长度和元素的类型。例如,[4]int表示一个四个整数的数组。数组的大小是固定的,数组的大小是类型的一部分,也就是说[4]int 和 [5]int是不同的类型,不能比较。数组可以按序号索引访问,从0开始,s[n]表示访问第n个元素。

    var a [4]int
    
    a[0] = 1
    
    i := a[0]
    
    // i == 1

    数组不需要明确的初始化,默认是数组元素类型的零值: 

    // a[2] == 0

    [4]int的内存分布为:

    Go的数组是值语义,即数组变量代表整个数组,并不是一个指向数组第一个元素的指针(C语言)。这意味着当赋值或者传递数组时将会产生数组内容拷贝。

    数组指定元素初始化:

    b := [2]string{"Penn", "Teller"

    或者:

    b := [...]string{"Penn", "Teller"}

    上面两种写法b的类型都是[2]string

    package main
    
    import
    "fmt"

    func main() { b := [...]string{"Penn", "Teller"} fmt.Printf("%T ", b) }

    程序输出:

    [2]string

    Slices切片

    由于数组的大小是固定的,不是很灵活,所以在Go的代码里面不会经常出现。但是,slice是随处可见的。slice是数组的基础进行了封装,更加强大和方便。 

    切片的定义为:[]T,其中T是切片元素的类型。不像数组,切片类型不用指定长度。例如:

    letters := []string{"a", "b", "c", "d"}

    letters的类型为[]string,而不是[4]string

    切片可以用内建函数make创建,make的签名为:

    func make([]T, len, cap) []T

    其中cap可选

    var s []byte
    
    s = make([]byte, 5, 5)
    
    // s == []byte{0, 0, 0, 0, 0}

    不指定cap:

    s := make([]byte, 5)

    切片的零值是nil。对零值调用len和cap函数将会返回0.

    切片可以从一个已经存在的切片或者数组中切分生成,切分遵循的是半开闭原则,例如b[1:4]表达式创建一个切片包含b的序号1到3的元素,由此得到新的切片序号将会是从0到2。 

    b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
    
    // b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

    用于表示切分的开始和结束的序号都是可选的,默认分别是0和切片的长度。例如:

    // b[:2] == []byte{'g', 'o'}
    
    // b[2:] == []byte{'l', 'a', 'n', 'g'}
    
    // b[:] == b

    从数组中切分为切片:

    package main
    
     
    
    import "fmt"
    
     
    
    func main() {
    
     x := [3]string{"","","Gopher"}
    
     s := x[:]
    
     fmt.Println(s)
    
     fmt.Printf("x type:%T
    s type:%T
    ", x, s)
    
    }

    程序输出:

    [我 是 Gopher]
    
    x type:[3]string
    
    s type:[]string

    切片内部实现:

    切片是一个数组段的描述,由一个指向数组的指针,数据段的长度(length),和它的最大能容纳数据的大小(capacity):

    上面提到的s,一开始的时候由make([]byte, 5)创建时,结构如下:

     

    s=s[2:4]相关的结构体:

    切分不会拷贝切片的数组,将会创建一个新的切片值指向原始的数组。这使得切片的操作像数组索引访问一样高效。因此,更改重新切分的切片元素也会影响到原始的切片:

    d := []byte{'r', 'o', 'a', 'd'}
    
    e := d[2:] 
    
    // e == []byte{'a', 'd'}
    
    e[1] = 'm'
    
    // e == []byte{'a', 'm'}
    
    // d == []byte{'r', 'o', 'a', 'm'}

    如果是想让重新切分的切片拥有独立的内存数据,可以使用copy函数:

    func copy(dst, src []T) int

    例如:

    t := make([]byte, len(s), (cap(s)+1)*2)
    
    copy(t, s)
    
    s = t 

    Map哈希表/字典

    计算机科学中哈希表是一个很重要的数据结构,Go提供了内建的map类型用于实现哈希表。

    Go map类型长这样:

    map[KeyType]ValueType

    其中KeyType需要是可比较类型,可比较类型:

    可比较类型是值可以用==和!=操作比较的类型,有:

    Boolean 值是可以比较的。两个boolean值如果都是true或者都是false,那么它们就是相等的。

    Interger,Float 值是可以比较的。

    Complex 值是可以比较的。如果real(u) == real(v) 并且 imag(u) == imag(v),那么两个complex值相等。

    String 值是可以比较的。

    Pointer值是可以比较的。如果两个指针值指向同一个变量那么这两个指针值相等,或者都是nil。

    Channel 值是可以比较的。

    Interface值是可以比较的。如果两个interface值得concrete type和value都相等(前提是concrete type是可比较的),那么这两个interface相等。如果两个interface都是nil那么也相等。

    var a1 int = 3
    
    var a2 int = 3
    
    var ia1 interface{}
    
    var ia2 interface{}
    
    ia1 = a1
    
    ia2 = a2
    
    if ia1 == ia2 {
    
     fmt.Println("equal")
    
    }

    程序输出:

    equal

    Struct值是可比较的,前提是每个字段都是可比较的。如果两个struct的每个字段都相等,那么这两个struct相等。

    type ST struct {
    
     name string
    
     age int
    
    }
    
    s1 := ST{"tom", 19}
    
    s2 := ST{"tom", 19}
    
    fmt.Println(s1 == s2)

    程序输出:

    true

    数组值是可以比较的,前提是数组元素类型是可以比较的。当两个数据的每个元素都对应相等,那么这两个数组是相等的。

    a1 := [2]string{"iam", "handsome"}
    
    a2 := [2]string{"iam", "handsome"}
    
    fmt.Println(a1 == a2)

    程序输出:

    true

    需要特别说明的是如果两个interface指向的实际类型(concrete type)是不可比较类型,如果比较这两个interface将会触发运行时panic,例如:

    a1 := []int{1,3}
    
    a2 := []int{1,3}
    
    var ia1 interface{}
    
    var ia2 interface{}
    
    ia1 = a1
    
    ia2 = a2
    
    if ia1 == ia2 {
    
     fmt.Println("equal")
    
    } 

    程序运行结果:

    panic: runtime error: comparing uncomparable type []int
    
     
    
    goroutine 1 [running]:
    
    main.main()
    
     /Users/haiweilu/saas/src/awesomeProject/channel/main.go:24 +0xb2

    Slice,map和function 值是不能比较的。但是有一个特例,就是可以和nil比较,判断slice,map和function是否是nil。 

    所以slice,map和funciton值不能作为map的key。map的ValueType可以是任意类型,当然也包括map类型。例如:

    var m map[string]int

    Map类型是引用类型,类似于指针和切片,所以上述m的值是nil,它指向一个还没初始化的map,即map的零值是nil。对nil map值进行读写访问会触发运行时panic。为了避免这种情况,可以用内建函数make创建map:

    m = make(map[string]int)

    Make函数分配并初始化一个哈希表数据结构,并且返回一个指向这个结构的map值。

    Map的使用

    设置一个key为”route”,value为66的元素:

    m["route"] = 66

    根据key索引访问value:

    i := m["route"]

    如果key对应的value不存在,将会返回该value类型对应的零值,例如:

    j := m["root”],j的值是0

    求map的元素数量:

    n := len(m)

    根据key删除map对应的k-v:

    delete(m, "route")

    可以用”common,ok”表达式判断map的key是否存在:

    _, ok := m["route"]

    如果”route”存在,ok为true,否则为false

    遍历map:

    for key, value := range m {
    
        fmt.Println("Key:", key, "Value:", value)
    
    }

    初始化map的另外一种方法:

    commits := map[string]int{
    
        "rsc": 3711,
    
        "r":   2138,
    
        "gri": 1908,
    
        "adg": 912,
    
    }
    
    m = map[string]int{} 

    用map实现set

    由于map索引对应key不存在时返回value类型的零值,所以我们可以用map[KeyType]bool来实现一个set 

    struct作为map的key实现多维索引

    例如:

    type Key struct {
    
        Path, Country string
    
    }
    
    hits := make(map[Key]int)
    
     
    
    hits[Key{"/", "vn"}]++
    也可以这样:
    
    n := hits[Key{"/ref/spec", "ch"}]

    Map的并发

    Map的操作不是原子操作,所以多个goroutine并发读写map会导致运行时panic。同时读没有问题。可以通过读写锁的方式实现同步并发读写:

    var counter = struct{
    
        sync.RWMutex
    
        m map[string]int
    
    }{m: make(map[string]int)}
    
     
    
    读:
    
    counter.RLock()
    
    n := counter.m["some_key"]
    
    counter.RUnlock()
    
    fmt.Println("some_key:", n)
    
     
    
    写:
    
    counter.Lock()
    
    counter.m["some_key"]++
    
    counter.Unlock()

    有序map

    Map中的key不保证顺序,也就说保证每次遍历同一个map的key返回顺序都是一致的,如果需要key是有序的可以通过增加一个辅助的切片来实现:

    import "sort"
    
     
    
    var m map[int]string
    
    var keys []int
    
    for k := range m {
    
        keys = append(keys, k)
    
    }
    
    sort.Ints(keys)
    
    for _, k := range keys {
    
        fmt.Println("Key:", k, "Value:", m[k])
    
    }

    总结

    文档介绍了array、slice和map的各种使用场景,希望能够帮助大家少点踩坑。 

    参考

    https://blog.golang.org/go-slices-usage-and-internals

    https://blog.golang.org/go-maps-in-action
    https://golang.org/ref/spec#Comparison_operators 

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=thg523juerih

  • 相关阅读:
    LeetCode "Palindrome Partition II"
    LeetCode "Longest Substring Without Repeating Characters"
    LeetCode "Wildcard Matching"
    LeetCode "Best Time to Buy and Sell Stock II"
    LeetCodeEPI "Best Time to Buy and Sell Stock"
    LeetCode "Substring with Concatenation of All Words"
    LeetCode "Word Break II"
    LeetCode "Word Break"
    Some thoughts..
    LeetCode "Longest Valid Parentheses"
  • 原文地址:https://www.cnblogs.com/makelu/p/11323662.html
Copyright © 2011-2022 走看看