Golang高效实践之array、slice、map实践

Stella981
• 阅读 685

前言

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的内存分布为:

Golang高效实践之array、slice、map实践

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\n", 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\ns type:%T\n", x, s)

}

程序输出:

[我 是 Gopher]

x type:[3]string

s type:[]string

切片内部实现:

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

Golang高效实践之array、slice、map实践

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

Golang高效实践之array、slice、map实践  

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

Golang高效实践之array、slice、map实践

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Kevin501 Kevin501
3年前
Go语言中new()和make()的区别
1.Go语言中的值类型和引用类型值类型:int,float,bool,string,struct和数组(数组要特别注意,别搞混了)变量直接存储值,分配栈区的内存空间,这些变量所占据的空间在函数被调用完后会自动释放。引用类型:slice,map,chan和值类型对应的指针变量存储的是一个地址(或者理解为指针),指针指向内存中真
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这