Go part 4 数据容器(数组,slice,string,map,syncMap,list)
数组
数组是值类型,因此改变副本的值,不会影响到本身
数组的定义:var 变量名 [元素数量] T
- 变量名(符合标识符要求即可)
- 元素数量(整型,可以是const中的值)
- T(可以是任意基本类型,包括数组本身,当类型为数组时,可以实现多维数组)
- var a [5]int 和 var a [10]int 是不同的类型
定义一个字符串数组,然后赋值:
var team [3]string
team[0] = "hammer"
team[1] = "soldier"
team[2] = "mum"
fmt.Println(team) 输出结果:
[hammer soldier mum]用
也可以在声明时进行元素赋值:
var team [3]string = [3]string{"hammer", "soldier", "mum"}
var team = [3]string{"hammer", "soldier", "mum"}
遍历数组:
打印索引值(访问越界,会触发panic)
var arrayA = [3]string{"hammer", "soldier", "mum"} for index, value := range arrayA {
fmt.Println(index, value)
} 运行结果:
0 hammer
1 soldier
2 mum
用匿名标识符忽略 index
var arrayA = [3]string{"hammer", "soldier", "mum"} for _, value := range arrayA {
fmt.Println(value)
} 运行结果:
hammer
soldier
mum
多维数组
func main(){
var manyArray [2][5]int = [2][5]int{{1,2,3,4,5}, {6,7,8,9,10}}
for rowIndex, rowValue := range manyArray{
for columnIndex, columnValue := range rowValue{
fmt.Printf("(%d %d)→%d ", rowIndex, columnIndex, columnValue)
}
}
} 运行结果:
(0 0)→1 (0 1)→2 (0 2)→3 (0 3)→4 (0 4)→5 (1 0)→6 (1 1)→7 (1 2)→8 (1 3)→9 (1 4)→10
数组排序
不能对数组进行排序
切片(Slice)
是一个拥有相同类型元素的可变长度的序列,是数组的引用(引用类型),包含地址,大小和容量,如下图,切片结构和内存分配:
从数组或切片生成新的切片
slice [start:end] start:开始位置索引,end:结束位置索引 (顾头不顾尾)
两个位置索引都缺省时,与数组或切片本身等效
两个位置索引都为0时,等效于空切片(用于清空元素)
var arrayA = [3]string{"a", "b", "c"}
fmt.Println("arrayA[:2]:", arrayA[:2]) 运行结果:
arrayA[:2]: [a b]
创建切片的两种方式(make 和 声明)
使用 make() 函数构造切片(推荐)
make([]T, size, cap)
- T(切片的元素类型)
- size(分配元素的数量)
- cap(预分配的元素数量)
var arrayA []int = make([]int, 2, 10)
fmt.Println(arrayA) // [0 0]
fmt.Println(len(arrayA)) // 2
使用 make() 函数生成的切片一定发生了内存分配操作。但从数组或切片中获取新切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作
声明新的切片
// 声明字符串切片
var strList []string // 声明整型切片
var numList []int // 声明一个空切片
var numListEmpty = []int{}
空切片不为 nil
声明新的切片后,可以使用 append() 函数来添加元素 (数组不行)
使用 append() 函数为切片添加元素
内建函数 append() 函数可以为切片添加元素,每个切片会指向一片内存空间,这片空间可以容纳一定数量的元素,当空间不足时,切片就会进行“扩容”,扩容操作往往发生在 append() 调用时
切片在扩容时,容量的扩展规律按容量的 2 倍扩充,例如 1,2,4,8,16...
var numSlice []int
for i:=0; i<10; i++{
numSlice = append(numSlice, i)
fmt.Printf("len:%d, cap:%d, pointer:%p\n", len(numSlice), cap(numSlice), numSlice)
} 运行结果:
len:1, cap:1, pointer:0xc00001c090
len:2, cap:2, pointer:0xc00001c0c0
len:3, cap:4, pointer:0xc0000162c0
len:4, cap:4, pointer:0xc0000162c0
len:5, cap:8, pointer:0xc000018200
len:6, cap:8, pointer:0xc000018200
len:7, cap:8, pointer:0xc000018200
len:8, cap:8, pointer:0xc000018200
len:9, cap:16, pointer:0xc00008e080
len:10, cap:16, pointer:0xc00008e080
len() 返回切片内元素数量,cap() 函数返回切片容量大小
往一个切片中不断添加元素的过程,类似于公司搬家。公司发展初期,资金紧张,人员很少,所以只需要很小的房间即可容纳所有的员工。随着业务的拓展和收入的增加就需要扩充工位,但是办公地的大小是固定的,无法改变。因此公司选择搬家,每次搬家就需要将所有的人员转移到新的办公点
- 员工和工位就是切片中的元素。
- 办公地就是分配好的内存。
- 搬家就是重新分配内存。
- 无论搬多少次家,公司名称始终不会变,代表外部使用切片的变量名不会修改。
- 因为搬家后地址发生变化,因此内存“地址”也会有修改
添加多个元素
numSlice = append(numSlice, 1, 2)
添加切片
var sliceA []string
var sliceB []string
sliceA = append(sliceA, "a")
sliceB = append(sliceB, "b", "c", "d")
sliceC := append(sliceA, sliceB...)
fmt.Println(sliceA, sliceB, sliceC) 输出结果:
[a] [b c d] [a b c d]
copy切片
内建函数 copy() ,可以将一个切片的数据复制到另外一个空间
使用格式:copy(destSlice, srcSlice)
package main
import(
"fmt"
) func main(){
const elementCount int = 10 var srcSlice []int = make([]int, elementCount) for i:=0;i<elementCount;i++{
srcSlice[i] = i
} // 引用切片数据
refSlice := srcSlice // 制造切片,copy 0到5,返回发生copy元素的数量
var copySlice []int = make([]int, elementCount)
num := copy(copySlice, srcSlice[:6])
fmt.Println("copy count:", num)
fmt.Println(srcSlice)
fmt.Println(copySlice) // 修改srcSlice的值,观察 refSlice 和 copySlice 值的变化
srcSlice[0]= 666
fmt.Printf("srcSlice[0]:%d, refSlice[0]:%d, copySlice[0]:%d \n", srcSlice[0], refSlice[0], copySlice[0])
} 运行结果:
copy count: 6 copy元素的数量
[0 1 2 3 4 5 6 7 8 9] srcSlice内容
[0 1 2 3 4 5 0 0 0 0] copySlice内容
srcSlice[0]:666, refSlice[0]:666, copySlice[0]:0
从切片中删除数据
并没有什么函数来提供删除切片元素,需要切片本身的特性来删除元素
例子:删除 Slice 中的 "c" 元素
package main import(
"fmt"
) func main(){
var seq []string = []string{"a", "b", "c", "d", "e"}
fmt.Printf("seq pointer:%p\n", seq)
subSeq1 := seq[:2]
fmt.Printf("subSeq1 pointer:%p\n", subSeq1)
subSeq2 := seq[3:]
fmt.Printf("subSeq2 pointer:%p\n", subSeq2) // 将截取的Slice连接起来,达到删除元素的效果
newSeq := append(subSeq1, subSeq2...)
fmt.Printf("newSeq:%v, pointer:%p\n", newSeq, newSeq)
} 运行结果:
seq pointer:0xc000090000
subSeq1 pointer:0xc000090000
subSeq2 pointer:0xc000090030
newSeq:[a b d e], pointer:0xc000090000
代码的删除过程,本质是以被删除元素为分界点,将前后两个部分的内存重新连接起来,如下图:
切片排序
导入 sort 包
- sort.Ints 整数类型排序
- sort.Strings 字符串排序
- sort.Float64s 浮点数排序
func main(){
var sliceInt []int = []int{4,6,2,7,8,1}
var sliceString []string = []string{"abc", "ab", "de", "d"}
var sliceFloat64 []float64 = []float64{2.34, 5.6, 1.23} sort.Ints(sliceInt)
sort.Strings(sliceString)
sort.Float64s(sliceFloat64) fmt.Printf("%v\n%v\n%v\n", sliceInt, sliceString, sliceFloat64)
} 运行结果:
[1 2 4 6 7 8]
[ab abc d de]
[1.23 2.34 5.6]
string
string 是值类型,底层是一个 byte 数组,因此,可以对 string 进行切片操作
Go 语言中,string本身是不可变的,如何改变 string 中的字符?
func main(){
var str string = "hello, world"
// 将字符串转换成字符切片
var slice []byte = []byte(str)
// 改变切片的值
slice[0] = 'o'
// 再将切片转换成 string 类型
str = string(slice)
fmt.Println(str)
} 运行结果:
oello, world
映射(map)
map 使用散列表(hash)实现,和 python 的 dictionary 一样,是无序的
创建 map 的两种方式(make 和 声明)
使用 make 创建一个 map(推荐)
func main(){
var scene map[string]int = make(map[string]int)
scene["route"] = 1
fmt.Println(scene["route"])
fmt.Println(scene["route2"])
} 运行结果:
1
0
声明 map 并填充内容(字面量方式)
就像 json 格式一样,冒号的左边是 key,右边是值,键值对之间使用逗号分隔(最后一个值后面的冒号也不能少)
func main(){
var mapA map[string]string
mapA = map[string]string{
"W": "forward",
"A": "left",
"D": "right",
"S": "backward",
} fmt.Println(mapA)
fmt.Println(mapA["W"])
} 运行结果:
map[A:left D:right S:backward W:forward]
forward
注意:map 声明后是 nil 值,必须要初始化,直接使用触发 panic: assignment to entry in nil map
var mapA map[string]string
mapA["name"]="johny"
查询某个 key 是否在 map 中存在
与 python 里面不同,如果 key 不存在会得到 value 类型的默认值
func main(){
var scene map[string]int = make(map[string]int)
scene["route"] = 1
value, ok := scene["route1"]
fmt.Println(value, ok)
} 运行结果:
0 false
遍历 map
map 的遍历过程也是使用 for range 完成
func main(){
var mapA map[string]string
mapA = map[string]string{
"W": "forward",
"A": "left",
"D": "right",
"S": "backward",
}
for key, value := range mapA{
fmt.Println(key, value)
}
} 运行结果:
W forward
A left
D right
S backward
只需要遍历键时:
for key := range mapA{
fmt.Println(key)
}
也可以使用匿名变量选择忽略键或值:
for key, _ := range mapA{
fmt.Println(key)
}
需要注意的是,遍历输出元素的顺序与填充顺序无关,不能期望 map 在遍历时返回某种期望顺序的结果,如果你得到了顺序的结果,也不要庆幸,也许是当前的数据量不够大
如果需要特定顺序的遍历结果,正确的做法是排序:把字典的 key 或 value 放入到切片中,然后进行排序
func main(){
var mapA map[string]string
mapA = map[string]string{
"W": "forward",
"A": "left",
"D": "right",
"S": "backward",
}
var sliceA []string
for key, _ := range mapA{
sliceA = append(sliceA, key)
} // 对切片进行排序
sort.Strings(sliceA)
fmt.Println(sliceA)
} 运行结果:
[A D S W]
map 的删除与清空
使用内建函数 delete() 从 map 中删除一组键值对(delete 没有返回值):
func main(){
var mapA map[string]string
mapA = map[string]string{
"W": "forward",
"A": "left",
"D": "right",
"S": "backward",
} delete(mapA, "W")
fmt.Println(mapA)
} 运行结果:
map[A:left D:right S:backward]
清空 map
but it didn't
唯一的方法就是重新 make 一个新 map,不用担心垃圾回收的效率,Go 语言中的并行垃圾回收效率比写一个清空函数高效多了
sync.Map
同时读写 map 的问题
map 在并发情况下,只读线程是安全的,同时读写线程不安全
下面来看下并发情况下读写 map 时会出现的问题:
package main // 不停的对map进行写入
func write(mapA map[int]int){
for {
mapA[1] = 1
}
} func main(){
var mapA map[int]int = make(map[int]int)
go write(mapA)
// 不停的对map进行读取
for {
_ = mapA[1]
}
} 运行结果:
fatal error: concurrent map read and map write
运行时输出提示:并发的 map 读写,也就是说使用了两个并发函数不断地对 map 进行读和写而发生了竞态问题,map 内部会对这种并发操作进行检查并提前发现
引入 sync.Map
并发读写时,一般的做法是加锁,但这样性能并不高,Go 在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,与 map 不同,它不是以语言原生形态提供,而是在 sync 包下的特殊结构
声明一个 sync.Map(不能使用 make 创建)
var scene sync.Map
sync.Map 特性:
- 不能使用 map 的操作方式不同,是使用 sync.Map 的方法进行调用,Store 用来存储,Load 用来获取,Delete 用来删除
- 遍历:使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数回调函数的返回值是 bool 类型,需要继续迭代时,返回 true,终止迭代时,返回 false
- 没有提供获取数量的方法,替代方法是遍历时自行计算数量
- sync.Map 为了保证并发安全,会有一些性能损失,因此在非并发情况下,使用 map
并发安全的 sync.Map 演示代码如下:
func main(){
var syncMap sync.Map // 设置值
syncMap.Store("hebei", "beijing")
syncMap.Store("hubei", "wuhan")
syncMap.Store(1, "shenzhen") // 取值
value1, err1 := syncMap.Load("hubei")
value2, err2 := syncMap.Load("err")
fmt.Println(value1, err1)
fmt.Println(value2, err2) // 删除值(没有返回值)
syncMap.Delete("hubei") // 遍历 sync.Map 中的键值对(只取第一对)
syncMap.Range(func(key, value interface{}) bool {
fmt.Println("iterate:", key, value)
if key == "hebei"{
return false
} else {
return true
}
})
} 运行结果:
wuhan true
<nil> false
iterate: hebei beijing
列表(list)
和 python 的列表很类似,它可以存放任意类型,可以插入数据,可以移除数据,可以获取数据(不能通过索引取值)
有两种方式初始化列表
通过 container/list 包的 New 方法:
listA := list.New()
直接声明一个 list:
var listA list.List
列表插入数据
双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack
func main(){
var listA list.List
// 返回一个元素句柄 (*list.Element 结构)
element1 := listA.PushFront("first")
element2 := listA.PushBack(666)
fmt.Println(element1)
fmt.Println(element2)
} 运行结果:
&{0xc0000701b0 0xc000070150 0xc000070150 first}
&{0xc000070150 0xc000070180 0xc000070150 666}
列表删除元素
列表的插入函数的返回值会提供一个 *list.Element 结构,这个结构记录着列表元素的值及和其他节点之间的关系等信息。从列表中删除元素时,需要用到这个结构进行快速删除
func main(){
var listA list.List
// 添加元素,返回一个元素句柄 (*list.Element 结构)
element := listA.PushFront("first")
fmt.Println(element) // 删除元素,返回这个元素的值
ret := listA.Remove(element)
fmt.Println(ret)
} 运行结果:
&{0xc000084150 0xc000084150 0xc000084150 first}
first
遍历列表
遍历双链表需要配合 Front() 函数获取头元素句柄,Value 属性可以获取句柄的值,遍历时只要元素不为空就可以继续进行。每一次遍历调用元素的 Next
func main(){
var listA list.List
// 取句柄的值
element := listA.PushFront("first")
listA.PushBack(666)
fmt.Println(element.Value) // 遍历列表
for i:=listA.Front(); i!=nil; i=i.Next(){
fmt.Println(i.Value)
}
} 运行结果:
first
first
666
end~
Go part 4 数据容器(数组,slice,string,map,syncMap,list)的更多相关文章
- DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架
引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DDD使用产生的疑问: •没有正确的使用ORM, 导致数 ...
- SOD框架的数据容器,打造最适合DDD的ORM框架
SOD框架的数据容器,打造最适合DDD的ORM框架 引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DD ...
- java实验五——字符数组、String、StringBuffer的相互转化,StringBuffer的一些方法
package hello; import java.util.Scanner; public class 实验五 { public static void main(String[] args) { ...
- golang array, slice, string笔记
本来想写一篇关于golang io的笔记,但是在学习io之前必须了解array, slice, string概念,因此将在下篇写golang io. array: 数组的长度是该数组类型的一部分, ...
- go基础之基本数据结构(数组、slice、map)
go基本的数据结构有数组.slice.map,高级数据结构为结构体为用户自定义类型.本片文章主要讲解三大基本数据结构. 数组 slice Map 数组 数组是包含单个类型的元素序列,但是长度固定的数据 ...
- Netty 中的内存分配浅析-数据容器
本篇接续前一篇继续讲 Netty 中的内存分配.上一篇 先简单做一下回顾: Netty 为了更高效的管理内存,自己实现了一套内存管理的逻辑,借鉴 jemalloc 的思想实现了一套池化内存管理的思路: ...
- go 数组(array)、切片(slice)、map、结构体(struct)
一 数组(array) go语言中的数组是固定长度的.使用前必须指定数组长度. go语言中数组是值类型.如果将数组赋值给另一个数组或者方法中参数使用都是复制一份,方法中使用可以使用指针传递地址. 声明 ...
- 包装类、数组、string类浅析及练习
String s1 = "abc"; String s2 = "abc"; System.out.println(s1==s2); //返回true Strin ...
- 字节数组与String类型的转换
还是本着上篇文章的原则,只不过在Delphi中string有点特殊! 先了解一下Delphi中的string 1. string = AnsiString = 长字符串,理论上长度不受限制,但其实受限 ...
- 【delphi】Byte数组与String类型的转换
string string = AnsiString = 长字符串,理论上长度不受限制,但其实受限于最大寻址范围2的32次方=4G字节: 变量Str名字是一个指针,指向位于堆内存的字符序列,字符序列起 ...
随机推荐
- ElasticSearch 6 安装、下载
1,安装配置JDK 8 参考:官方文档 #为什么是JDK1.8?在ElasticSearch6.2.4中提到:JDK版本必须为:1.8.0_131 以上 > 1,安装JDK1.8-161 #解压 ...
- [Java复习] 设计模式 Design Pattern
设计模式的六大原则 1.开闭原则(Open Close Principle) 对扩展开放,对修改关闭. 2.里氏代换原则(Liskov Substitution Principle) 任何基类可以出现 ...
- PAT 甲级 1038 Recover the Smallest Number (30 分)(思维题,贪心)
1038 Recover the Smallest Number (30 分) Given a collection of number segments, you are supposed to ...
- redis未设置idle超时时间导致连接过多
今天ELK收集日志的时候,发现收集失败,查找各方面原因,最后在redis日志里面发现报错:[2489] 02 Jun 10:43:42 # Error allocating resoures for ...
- CSS History
Preface 如果只是要写程序,那的确是不需要这么麻烦,上来直接看Syntax,动手写上至少300行代码,做上3个应用程序,这门语言你也就差不多会用了,接下来不过就是模式,特殊的地方以及记住一些函数 ...
- 【C/C++开发】运算符重载
c++的一大特性就是重载(overload),通过重载可以把功能相似的几个函数合为一个,使得程序更加简洁.高效.在c++中不止函数可以重载,运算符也可以重载.由于一般数据类型间的运算符没有重载的必要, ...
- 【ARM-Linux开发】使用QT和Gstreanmer 遇到的一些问题
1.如果出现错误,可能是在安装UCT PCRF时,相关组件不全,略举两个碰到的错误. 1)curl/curl.h:No such file or directory --可能原因是libcurl及相关 ...
- easyui-numberspinner实现双箭头效果
效果图: 实现了 [点击左上角 输入框的值加 0.5] [ 左下角 值减0.5 ] [ 右上角点击 值加1] [ 右下角点击 值减1] 代码: <span style="positio ...
- cent8安装postgres
postgres是一款免费.开源的对象型关系数据库,其在cent8的安装方式与cent7的不太一样,特此记录. 步骤: 1 安装postgres server dnf install postgres ...
- python3.7 完美安装
在安装python3.7的过程中,我发现如果不加注意,pip3是无法被安装的.而这就不能算是完整安装python3了. 所以,我总结一下,如何完美安装python3.7. 依赖 yum insta ...