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 []int a[] = i := a[] // i == 1
数组不需要明确的初始化,默认是数组元素类型的零值:
// a[2] == 0
[4]int的内存分布为:

Go的数组是值语义,即数组变量代表整个数组,并不是一个指向数组第一个元素的指针(C语言)。这意味着当赋值或者传递数组时将会产生数组内容拷贝。
数组指定元素初始化:
b := []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, , )
// s == []byte{0, 0, 0, 0, 0}
不指定cap:
s := make([]byte, )
切片的零值是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 := []string{"我","是","Gopher"}
s := x[:]
fmt.Println(s)
fmt.Printf("x type:%T\ns type:%T\n", x, s)
}
程序输出:
[我 是 Gopher] x type:[]string s type:[]string
切片内部实现:
切片是一个数组段的描述,由一个指向数组的指针,数据段的长度(length),和它的最大能容纳数据的大小(capacity):

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

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

切分不会拷贝切片的数组,将会创建一个新的切片值指向原始的数组。这使得切片的操作像数组索引访问一样高效。因此,更改重新切分的切片元素也会影响到原始的切片:
d := []byte{'r', 'o', 'a', 'd'}
e := d[:]
// e == []byte{'a', 'd'}
e[] = '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)+)*) 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 =
var a2 int =
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", }
s2 := ST{"tom", }
fmt.Println(s1 == s2)
程序输出:
true
数组值是可以比较的,前提是数组元素类型是可以比较的。当两个数据的每个元素都对应相等,那么这两个数组是相等的。
a1 := []string{"iam", "handsome"}
a2 := []string{"iam", "handsome"}
fmt.Println(a1 == a2)
程序输出:
true
需要特别说明的是如果两个interface指向的实际类型(concrete type)是不可比较类型,如果比较这两个interface将会触发运行时panic,例如:
a1 := []int{,}
a2 := []int{,}
var ia1 interface{}
var ia2 interface{}
ia1 = a1
ia2 = a2
if ia1 == ia2 {
fmt.Println("equal")
}
程序运行结果:
panic: runtime error: comparing uncomparable type []int goroutine [running]: main.main() /Users/haiweilu/saas/src/awesomeProject/channel/main.go: +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"] =
根据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": ,
"r": ,
"gri": ,
"adg": ,
}
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
Golang高效实践之array、slice、map的更多相关文章
- Golang高效实践之泛谈篇
前言 我博客之前的Golang高效实践系列博客中已经系统的介绍了Golang的一些高效实践建议,例如: <Golang高效实践之interface.reflection.json实践>&l ...
- Golang 高效实践之并发实践context篇
前言 在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel.比如说我们可以用channel来控 ...
- Golang 高效实践之并发实践
前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面 ...
- go - 复合类型 array, slice, map
Go 语言支持复合类型: 数组:array 切片:slice 指针:pointer 字典:map 通道:chan 结构体:struct 接口:interface 1. array 同一类型数据的集 ...
- Golang高效实践之interface、reflection、json实践
前言 反射是程序校验自己数据结构和类型的一种机制.文章尝试解释Golang的反射机制工作原理,每种编程语言的反射模型都是不同的,有很多语言甚至都不支持反射. Interface 在将反射之前需要先介绍 ...
- Golang 高效实践之defer、panic、recover实践
前言 我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑.但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃.Golang有没有一种异常 ...
- golang array, slice, string笔记
本来想写一篇关于golang io的笔记,但是在学习io之前必须了解array, slice, string概念,因此将在下篇写golang io. array: 数组的长度是该数组类型的一部分, ...
- golang的array/slice
相同点 由相同类型的元素组合构成 元素有序排列,0为第一个元素下标 基本使用方法相同 区别 array声明时需要指定容量大小,而且无法修改 slice可通过append增加元素,当容量不够时,会自动扩 ...
- 【GoLang】golang 最佳实践汇总
最佳实践 1 包管理 1.1 使用包管理对Golang项目进行管理,如:godep/vendor等工具 1.2 main/init函数使用,init函数参考python 1.2.1 main-> ...
随机推荐
- 使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理
使用SpringSecurity搭建授权认证服务(1) -- 基本demo 登录认证是做后台开发的最基本的能力,初学就知道一个interceptor或者filter拦截所有请求,然后判断参数是否合理, ...
- vue集成百度富文本编辑器
1.前期工作,访问百度富文本官网下载相应的百度富文本文件,根据后端用的技术下载相应的版本,建议下载最新版UTF-8版 (有图有真相,看图) https://ueditor.baidu.com/webs ...
- Java学习笔记之---基础语法
Java学习笔记之---基础语法 一. Java中的命名规范 (一)包名 由多个单词组成时,所有字母小写(例如:onetwo) (二)类名和接口 由多个单词组成时,所有单词首字母大写(例如:OneTw ...
- uSID:SRv6新范式
摘要:本文介绍最新的SRv6创新uSID(Micro Segment).uSID兼容既有的SRv6框架,将极大地改变SRv6的设计.实现和部署方式,成为SRv6的新范式. 一.SRv6 101 Seg ...
- BI之路学习笔记1--SSIS包的认识和设计
进入了新的公司,开始接触新的方向,内心激动而又兴奋,对于BI以前知道的极少,从今天开始要好好学习了~ BI的概念,功能,强大之处在此先不做赘述,BI之路先要一步一个脚印扎实做起,现在正在看的也是之前好 ...
- web前端笔试篇(一)
[ 题外话 ]:本博主作为一名准毕业生,即将面临毕业就业问题,即将到大四了,不准备考研的我,那么该去干嘛呢?毫无疑问,那就是实习,那么即使是实习,那么在要想进入自己心仪的企业之前,笔试这一关终究是无法 ...
- 鸽巢原理及其扩展——Ramsey定理
第一部分:鸽巢原理 咕咕咕!!! 然鹅大家还是最熟悉我→ a数组:but 我也很重要 $:我好像也出现不少次 以上纯属灌水 文章简叙:鸽巢原理对初赛时的问题求解以及复赛的数论题目都有启发意义.直接的初 ...
- Excel催化剂100+大主题功能梳理导读
Excel催化剂历经1年4个月的开发时间,终于荣登100+个大主题功能,完成数据领域的功能大矩阵,可以说在日常的数据处理及分析上,绝大部分的共性场景已经囊括其中,是数据工作者难得一遇的优秀作品之一.因 ...
- 个人永久性免费-Excel催化剂功能第102波-批量上传本地图片至网络图床(外网可访问)
自我突破,在100+功能后,再做有质量的功能,非常不易,相对录制视频这些轻松活,还是按捺不住去写代码,此功能虽小,但功课也做了不少,希望对真正有需要的群体带来一些惊喜. 背景介绍 图床的使用,一般是写 ...
- Git更改提交
提交记录我们的工作历史记录,提交自身是一成不变的.Git提供了几个工具和命令,抓门用来帮助修改完善版本库中的提交. 实际工作中存在很多情况需要我们去修改或返工某个提交或者整个提交序列: 1,可以在某个 ...