数组

数组是值类型,因此改变副本的值,不会影响到本身

数组的定义: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 特性:

  1. 不能使用 map 的操作方式不同,是使用 sync.Map 的方法进行调用,Store 用来存储,Load 用来获取,Delete 用来删除
  2. 遍历:使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数回调函数的返回值是 bool 类型,需要继续迭代时,返回 true,终止迭代时,返回 false
  3. 没有提供获取数量的方法,替代方法是遍历时自行计算数量
  4. 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)的更多相关文章

  1. DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架

    引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DDD使用产生的疑问: •没有正确的使用ORM, 导致数 ...

  2. SOD框架的数据容器,打造最适合DDD的ORM框架

    SOD框架的数据容器,打造最适合DDD的ORM框架 引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DD ...

  3. java实验五——字符数组、String、StringBuffer的相互转化,StringBuffer的一些方法

    package hello; import java.util.Scanner; public class 实验五 { public static void main(String[] args) { ...

  4. golang array, slice, string笔记

    本来想写一篇关于golang io的笔记,但是在学习io之前必须了解array, slice, string概念,因此将在下篇写golang io.   array: 数组的长度是该数组类型的一部分, ...

  5. go基础之基本数据结构(数组、slice、map)

    go基本的数据结构有数组.slice.map,高级数据结构为结构体为用户自定义类型.本片文章主要讲解三大基本数据结构. 数组 slice Map 数组 数组是包含单个类型的元素序列,但是长度固定的数据 ...

  6. Netty 中的内存分配浅析-数据容器

    本篇接续前一篇继续讲 Netty 中的内存分配.上一篇 先简单做一下回顾: Netty 为了更高效的管理内存,自己实现了一套内存管理的逻辑,借鉴 jemalloc 的思想实现了一套池化内存管理的思路: ...

  7. go 数组(array)、切片(slice)、map、结构体(struct)

    一 数组(array) go语言中的数组是固定长度的.使用前必须指定数组长度. go语言中数组是值类型.如果将数组赋值给另一个数组或者方法中参数使用都是复制一份,方法中使用可以使用指针传递地址. 声明 ...

  8. 包装类、数组、string类浅析及练习

    String s1 = "abc"; String s2 = "abc"; System.out.println(s1==s2); //返回true Strin ...

  9. 字节数组与String类型的转换

    还是本着上篇文章的原则,只不过在Delphi中string有点特殊! 先了解一下Delphi中的string 1. string = AnsiString = 长字符串,理论上长度不受限制,但其实受限 ...

  10. 【delphi】Byte数组与String类型的转换

    string string = AnsiString = 长字符串,理论上长度不受限制,但其实受限于最大寻址范围2的32次方=4G字节: 变量Str名字是一个指针,指向位于堆内存的字符序列,字符序列起 ...

随机推荐

  1. ES6展开运算符数组合并,函数传参

    定义: .展开运算符允许一个表达式在某处展开. 使用场景 1.展开函数在多个参数的地方使用 .意指用于函数传参 2.多个元素的地方使用,意指用于数组字面量 3.多个边框的地方使用,意指用于解构赋值 函 ...

  2. 一台java服务器可以跑多少个线程?

    一台java服务器能跑多少个线程?这个问题来自一次线上报警如下图,超过了我们的配置阈值.   京东自研UMP监控分析 打出jstack文件,通过IBM Thread and Monitor Dump ...

  3. C语言 消灭编译警告(Warning)

    如何看待编译警告 当编译程序发现程序中某个地方有疑问,可能有问题时就会给出一个警告信息.警告信息可能意味着程序中隐含的大错误,也可能确实没有问题.对于警告的正确处理方式应该是:尽可能地消除之.对于编译 ...

  4. STL函数适配器

    一:适配器简介 C++中有三类适配器,分别是容器适配器,迭代器适配器和函数适配器,这里主要介绍函数适配器. (一)函数适配器简介 STL中已经定义了大量的函数对象,但是有时候需要对函数返回值进行进一步 ...

  5. 【转】Selenium-WebDriverApi接口详解

    浏览器操作 # 刷新 driver.refresh() # 前进 driver.forward() # 后退 driver.back() 获取标签元素 # 通过ID定位目标元素 driver.find ...

  6. 123457123457#0#-----com.yimeng.wangZheChengYu01--前拼后广--成语头脑王者

    com.yimeng.wangZheChengYu01--前拼后广--成语头脑王者

  7. 123457123456#0#-----com.twoapp.KidsShiZi01--前拼后广--儿童宝宝识字jiemei

    com.twoapp.KidsShiZi01--前拼后广--儿童宝宝识字jiemei

  8. LODOP在页面让客户选择打印机

    获取打印机列表可以放在onload事件里,如过当前是使用的c-lodop,由于websoket链接需要时间,一进入页面可能会报错,被准备好或网页没下载完成等,也可以在点击事件里让用户获取打印机.之前写 ...

  9. SQL Server数据同步交换

    一.为了解决数据同步汇聚,数据分发,数据转换,数据维护等需求,TreeSoft将复杂的网状的同步链路变成了星型数据链路.     TreeSoft作为中间传输载体负责连接各种数据源,为各种异构数据库之 ...

  10. Fakes中Shim的2种方法

    Fakes自动生成的Shim代码,有两种可能:(目前尚不清楚生成规律) //属性型 public static FakesDelegates.Func<string, bool, string& ...