Go

Slice

slice 其实是一个结构体。

包含了三个成员:len, cap, array,分别表示切片长度,容量,底层数据的地址。

当 slice 作为函数参数时,就是一个普通的结构体。

其实很好理解:若直接传 slice,for … range slice 在调用者看来,实参 slice 并不会被函数中的操作改变;s[i]可以。

若传的是 slice 的指针,在调用者看来,是会被改变原 slice 的。

package main

func main() {
	s := []int{1, 1, 1}
	f(s)
	fmt.Println(s)
}

func f(s []int) {
	// i只是一个副本,不能改变s中元素的值
	/*for _, i := range s {
		i++
	}
	*/

	for i := range s {
		s[i] += 1
	}
}

s[i,j,k]

底层数组的开始处,结束处(开),容量处(开)

package main

import "fmt"

func main() {
	slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := slice[2:5] //[2,3,4] cap = 8
	// s2 从 s1 的索引2(闭区间)到索引6(开区间,元素真正取到索引5)
	// 容量到索引7(开区间,真正到索引6)
	s2 := s1[2:6:7] //[4,5,6,7] cap = 5

	s2 = append(s2, 100) //[4,5,6,7,100] cap = 5,对应底层数组修改
	s2 = append(s2, 200) // 容量扩容,底层数组不修改

	s1[2] = 20

	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(slice)
}
# output
#[2 3 20] cap 默认到数组尾部,所以为 8
#[4 5 6 7 100 200] 
#[0 1 2 3 20 5 6 7 100 9]

fmt

%4d 固定宽度,前面补空格
%-4d 固定宽度,后面补空格

Map

ageMp := make(map[string]int)
// 指定 map 长度
ageMp := make(map[string]int, 8)

// ageMp 为 nil,不能向其添加元素,会直接panic
var ageMp map[string]int

map 实现的几种方案,Go 语言采用的是哈希查找表,并且使用链表解决哈希冲突。

哈希查找表用一个哈希函数将 key 分配到不同的(bucket,也就是数组的不同 index)

Go 语言中只要是可比较的类型都可以作为 key。

除开 slice,map,functions 这几种类型,其他类型都是 OK 的。

具体包括:布尔值、数字、字符串、指针、通道、接口类型、结构体、只包含上述类型的数组。这些类型的共同特征是支持 == 和 != 操作符,k1 == k2 时,可认为 k1 和 k2 是同一个 key。

如果是结构体,只有 hash 后的值相等以及字面值相等,才被认为是相同的 key。很多字面值相等的,hash出来的值不一定相等,比如引用。

顺便说一句,任何类型都可以作为 value,包括 map 类型。

map 不是线程安全的

map 线程解决

哈希查找表用一个哈希函数将 key 分配到不同的桶(bucket,也就是数组的不同 index)。这样,开销主要在哈希函数的计算以及数组的常数访问时间。在很多场景下,哈希查找表的性能很高。

哈希查找表一般会存在“碰撞”的问题,就是说不同的 key 被哈希到了同一个 bucket。一般有两种应对方法:链表法和开放地址法。链表法将一个 bucket 实现成一个链表,落在同一个 bucket 中的 key 都会插入这个链表。开放地址法则是碰撞发生后,通过一定的规律,在数组的后面挑选“空位”,用来放置新的 key。

自平衡搜索树法的最差搜索效率是 O(logN),而哈希查找表最差是 O(N)。当然,哈希查找表的平均查找效率是 O(1)

最理想的情况是一个 bucket 只装一个 key,这样,就能达到 O(1) 的效率,但这样空间消耗太大,用空间换时间的代价太高。

因此,需要有一个指标来衡量前面描述的情况,这就是装载因子。Go 源码里这样定义 装载因子:

loadFactor := count / (2^B)

count 就是 map 的元素个数,2^B 表示 bucket 数量。

源码里定义的阈值是 6.5,最大为8(桶装满)
在这里插入图片描述
每一个桶都是一整片的内存空间,当发现桶中的 tophash 与传入键的 tophash 匹配之后,我们会通过指针和偏移量获取哈希中存储的键 keys[0] 并与 key 比较,如果两者相同就会获取目标值的指针 values[0] 并返回。
实现方式

Channel

通过汇编分析,我们知道,最终创建 chan 的函数是 makechan:

func makechan(t *chantype, size int64) *hchan

从函数原型来看,创建的 chan 是一个指针。所以我们能在函数间直接传递 channel,而不用传递 channel 的指针。

如果channel c已经被关闭,继续往它发送数据会导致panic: send on closed channel:

但是从这个关闭的channel中不但可以读取出已发送的数据,还可以不断的读取零值

但是如果通过range读取,channel关闭后for循环会跳出:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
	fmt.Println(i)
}

通过 i, ok := <-c 可以查看Channel的状态,判断值是零值还是正常读取的值。

c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

无缓冲通道关闭后, 可供 select 使用:

package main

import (
	"fmt"
	"time"
)

func hello(c chan interface{}) {

	time.Sleep(3*time.Second)
	close(c)
}

func main() {
	c := make(chan interface{})
	
	go hello(c)
	
	select {
		case <- c:
			break
		case <-time.After(5*time.Second):
			return
	}
	
	fmt.Println("main function")
}

default

  1. select语句不使用default分支时,处于阻塞状态直到其中一个channel的收/发操作准备就绪(或者channel关闭或者缓冲区有值),如果同时有多个channel的收/发操作准备就绪(或者channel关闭)则随机选择其中一个。
  2. select语句使用default分支时,处于非阻塞状态,从所有准备就绪(或者channel关闭或者缓冲区有值)的channel中随机选择其中一个,如果没有则执行default分支。

堆栈

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值