golang没有INT_MAX INT_MIN常量值,可以用位操作运算
1. 无符号整型uint
最小值0,二进制所有位都为0
const UINT_MIN uint = 0
最大值的二进制所有位为1
const UINT_MAX = ^uint(0)
2. 有符号整型int
最大值,根据二进制补码,第一位为0,其余为1
const INT_MAX = int(^uint(0) >> 1)
最小值,第一位为1,其余为0,最大值取反即可
const INT_MIN = ^INT_MAX
golang中byte数据类型与rune相似,它们都是用来表示字符类型的变量类型。它们的不同在于:
- byte 等同于int8,常用来处理ascii字符
- rune 等同于int32,常用来处理unicode或utf-8字符
如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本
内容复制
数组切片支持Go语言的另一个内置函数 copy() ,用于将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。下面的示例展示了 copy() 函数的行为:
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
问题1 直接使用值为 nil 的 slice、map
var m map[int]int
m[1] = 2
main.main()
/home/lin/project/test.go:46 +0x31d
exit status 2
允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素则会造成运行时 panic
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
if h == nil {
panic(plainError("assignment to entry in nil map"))
}
问题2 map
map 容量
在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap()
来检测分配空间的大小
m := make(map[string]int, 99)
println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap
更新 map 字段的值
如果 map 一个字段的值是 struct 类型,则无法直接更新该 struct 的单个字段:因为 map 中的元素是不可寻址的
多个协程并发访问一个map,有可能会导致程序退出,并打印下面错误信息:
fatal error: concurrent map read and map write
并发访问map是不安全的,会出现未定义行为,导致程序退出。所以如果希望在多协程中并发访问map,必须提供某种同步机制,一般情况下通过读写锁sync.RWMutex实现对map的并发访问控制
问题3 数组类型的值作为函数参数
数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的
如果想修改参数数组:
- 直接传递指向这个数组的指针类型
x := [3]int{1,2,3}
func(arr *[3]int) {}
二维数组:
table := make([][]int, x)
for i := range table {
table[i] = make([]int, y)
}
问题3 string 类型的值是常量,不可更改
string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转string
问题4 字符串的长度
Go 的内建函数 len()
返回的是字符串的 byte 数量
问题5 对内建数据结构的操作并不是同步的
goroutine 和 channel 是进行原子操作的好方法,或使用 "sync" 包中的锁
问题6 channel
向已关闭的 channel 发送数据会造成 panic,从已关闭的 channel 接收数据是安全的
在一个值为 nil 的 channel 上发送和接收数据将永久阻塞
问题7 在 range 迭代 slice、array、map 时通过更新引用来更新元素
在 range 迭代中,得到的值其实是元素的一份值拷贝,更新拷贝并不会更改原来的元素,即是拷贝的地址并不是原有元素的地址
问题8 slice
从 slice 中重新切出新 slice 时,新 slice 会引用原 slice 的底层数组。如果跳了这个坑,程序可能会分配大量的临时 slice 来指向原底层数组的部分数据,将导致难以预料的内存使用
可以通过拷贝临时 slice 的数据,而不是重新切片来解决
向一个 slice 中追加元素而它指向的底层数组容量不足时,将会重新分配一个新数组来存储数据。而其他 slice 还指向原来的旧底层数组。
// 超过容量将重新分配数组来拷贝值、重新存储
func main() {
s1 := []int{1, 2, 3}
fmt.Println(len(s1), cap(s1), s1) // 3 3 [1 2 3 ]
s2 := s1[1:]
fmt.Println(len(s2), cap(s2), s2) // 2 2 [2 3]
for i := range s2 {
s2[i] += 20
}
// 此时的 s1 与 s2 是指向同一个底层数组的
fmt.Println(s1) // [1 22 23]
fmt.Println(s2) // [22 23]
s2 = append(s2, 4) // 向容量为 2 的 s2 中再追加元素,此时将分配新数组来存
for i := range s2 {
s2[i] += 10
}
fmt.Println(s1) // [1 22 23] // 此时的 s1 不再更新,为旧数据
fmt.Println(s2) // [32 33 14]
}
问题9 defer
对 defer 延迟执行的函数,它的参数会在声明时候就会求出具体值,而不是在执行时才求值:
// 在 defer 函数中参数会提前求值
func main() {
var i = 1
defer fmt.Println("result: ", func() int { return i * 2 }())
i++
}
问题10 new和make区别
new 的作用是初始化一个指向类型的指针(*T)。使用new函数来分配空间。传递给new函数的是一个类型,不是一个值。返回值是 指向这个新分配的零值的指针
make 的作用是为 slice,map 或 chan 初始化并返回引用(T)
问题11 进程和线程、协程的区别
进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全
线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据(线程是处理器调度的基本单位)
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
问题12 struct{}类型值的表示法只有一个,即:struct{}{}。
它占用的内存空间是0字节,这个值在整个Go 程序中永远都只会存在一份。虽然我们可以无数次地使用这值字面量,但是用到的却都是同一个值。
问题13 string 与 byte转化问题
https://www.jianshu.com/p/648a6d3bbb24
问题14 tool pprof使用
go tool pprof -alloc_space -cum -svg http://127.0.0.1:9988/debug/pprof/heap > heap.svg
go tool pprof --alloc_objects http://127.0.0.1:9988/debug/pprof/heap
if debug {
go func() {
http.ListenAndServe("0.0.0.0:9988", nil)
}()
}
问题15 内存回收机制 mark and sweep(标记-清除)算法
该算法法分为两步:
- 标记从根变量开始迭代得遍历所有被引用的对象,对能够通过应用遍历访问到的对象都进行标记为“被引用”;
- 清除是在标记完成后进行的操作,对没有标记过的内存进行回收(回收同时可能伴有碎片整理操作)。
三色标记法优化了这个问题
问题16 报错cannot find package "gopkg.in/go-playground/validator.v9"
go语言第三方包依赖管理工具govendor,报错cannot find package "gopkg.in/go-playground/validator.v9"
比如,gopkg.in/go-playground/validator.v9 这个包,实际对应的就是 github.com/go-playground/validator 的 v9 分支,那么只要 git clone -b v9 https://github.com/go-playground/validator.git 就可以拉下来。
参考:
https://segmentfault.com/a/1190000013739000