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

  • 相关阅读:
    python多进程(一)
    python操作memcached
    python操作redis
    SQLAlchemy总结
    SQLAlchemy-ORM
    python操作mysql二
    python操作mysql
    python正则二
    python正则
    python内置模块(三)
  • 原文地址:https://www.cnblogs.com/makelu/p/11323662.html
Copyright © 2011-2022 走看看