一、各数据类型内存布局
- Sizeof(uint8) = 1 字节 = 8byte
+-+
|1|
+-+
|
+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|1|
+-+-+-+-+-+-+-+-+
- Sizeof(rune) = 4
+-----+
| 4 |
+-----+
|
|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|0|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
- Sizeof(int64) = 8
+-----+
| 8 |
+-----+
|
|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|1|0|0|0|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
- Sizeof(float64)=8
+-----+
| 8 |
+-----+
|
|
同上
- Sizeof(string) = 8|16 区分32/64位服务器
var s string = "hello"
+---------+---------+
| Pointer | len=5 | # pointer和len各占4|8个字节
+---------+---------+
|
|
+---+---+---+---+---+
| h | e | l | l | o | # [5]byte
+---+---+---+---+---+
- struct
struct{a byte; b byte; c int32} = {1,2,3}
+---+---+---+---+---+---+---+---+
| 1 | 2 | 0 | 0 | 3 | 0 | 0 | 0 | # 内存对齐原则
+---+---+---+---+---+---+---+---+
struct{a *int; b int}
+-----------+-----+
| pointer a | b |
+-----------+-----+
|
|
+-----+
| int |
+-----+
- Sizeof(slice)=24
x = []int{0,1,2,3,4,5,6,7}
+---------+---------+---------+
| Pointer | len=8 | cap=8 | # 各占8个字节
+---------+---------+---------+
|
|
+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | # [8]int
+---+---+---+---+---+---+---+---+
|
|
+---------+---------+---------+
| Pointer | len=2 | cap=5 | # x[1:3:6]
+---------+---------+---------+
- Sizeof(interface{}) = 16
var in interface{}
+---------+---------+
| *itab | *data | # 各占8个字节
+---------+---------+
| |
| |
+------+ +------+
| Itab | | data |
+------+ +------+
- new
var n = new([3]int)
+---------+
| pointer | #占8个字节
+---------+
|
|
+---+---+---+
|0 | 0 | 0 |
+---+---+---+
- make
slice = make([]int,1,3)
+---------+---------+---------+
| pointer | len=1 | cap=3 | # 各占8个字节
+---------+---------+---------+
|
|
+---+---+---+
|0 | 0 | 0 | # [3]int
+---+---+---+
channel = make(chan int);
+---------+
| pointer | # 实际返回的是一个指针包装对象
+---------+
|
|
+---------------+
| chan.c Hchan |
+---------------+
二、基础概念总结(持续更新...)
-
1、unsafe.sizeof总是在编译期就进行求值,而不是在运行时,这意味着,sizeof的返回值可以赋值给常量。字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是4/ 8 个字节,所以字符串类型大小为 8/16 个字节(32bit/64bit)
字符串表示一个包含0个或者多个字符序列的串,在一个字符串内部,每个字符都表示成一个或者多个UTF-8编码的字节。 -
2、string 类型的值是只读的二进制 byte slice,不可按照索引号直接修改。字符串类型的零值是空串 "" if str =="" {true}
-
3、在 C/C++ 中,数组(名)是指针。将数组作为参数传进函数时,相当于传递了数组内存地址的引用,在函数内部会改变该数组的值。
在 Go 中,数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的。 如果想要修改数组参数,方法有两个: 1)直接传递指向这个数组的指针类型。2)使用slice:即使函数内部得到的是slice的值拷贝,但是依旧会更新slice的原始数据。 -
4、switch 语句中的 case 代码块会默认带上 break。
-
5、以小写字母开头的字段成员是无法被外部直接访问的。在进行json、xml、gob等格式的encode操作时,这些私有字段容易被忽略,导出时得到零值。
func StrEncodeTest() {
type MyData struct {
One int
two string
}
in := MyData{1, "data"}
fmt.Printf("%#v\n", in)
en, _ := json.Marshal(in)
fmt.Println(string(en))
var out MyData
json.Unmarshal(en, &out)
fmt.Printf("%#v\n", out)
}
-
6、slice 中隐藏的数据,从 slice 中重新切出新 slice 时,新 slice 会引用原 slice 的底层数组。如果跳了这个坑,程序可能会分配大量的临时 slice 来指向原底层数组的部分数据,将导致难以预料的内存使用。
-
7、select语句类似switch,但是select会公平随机执行选择一个可运行的case执行,如果没有可行对case,执行default。如果没有default,则就会阻塞。
-
8、使用const关键字定义常量,并不能使用:=标识符。
-
9、从Slice移除元素
# 移除单个元素
func (s *Slice) SliceRemove(value int) {
for i, v := range *s {
if v == value {
*s = append((*s)[:i], (*s)[i+1:]...) ##slice后面‘...’ 由于元素类型相同,因此append会提示使用..., 使用两个参数即可
}
}
}
# 移除多个元素
*s = append((*s)[:begin-1], (*s)[end:]...)
-
10、golang中数据类型(channel, complex, 函数) 不能转化为有效的JSON文本。
-
11、关于channel的特性,下面说法正确的是(ABCD)
A. 给一个 nil channel 发送数据,造成永远阻塞
B. 从一个 nil channel 接收数据,造成永远阻塞
C. 给一个已经关闭的 channel 发送数据,引起 panic
D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
- 12、For 语句中的迭代变量与闭包函数陷阱
# for语句中的迭代变量在每次迭代中都会崇勇,即for中创建闭包函数接受到的参数始终是同一个值,在goroutine开始执行时都会得到同一个迭代值。
fun main(){
data := []string{"one", "two", "three"}
for _, v := range data {
go func(){ //闭包
fmt.Println(v)
}()
}
time.Sleep(3*time.Second)
}
# 输出 three,three,three
# 解决方法:在for 内部实用局部变量保存迭代值,在传参:
for _, v :=range data {
vCopy := v
go func(){
fmt.Println(vCopy)
}()
}
- 13、Map 线程安全
在map并发情况下, 只读线程是安全的,同时读写是线程不安全的,严重时导致程序退出,错误信息如下:
fatal error: concurrent map read and map write。这个错误信息并不是每次都会遇到,并发访问的协成数量比较大时必然会出现。
所以建议使用sync.RWMutex读写锁实现对map的并发访问。
package main
import "sync"
type SafeMap struct {
sync.RWMutex
Map map[int]int
}
func main() {
safeMap := newSafeMap(10)
for i := 0; i < 100000; i++ {
go safeMap.writeMap(i, i)
go safeMap.readMap(i)
}
}
func newSafeMap(size int) *SafeMap {
sm := new(SafeMap)
sm.Map = make(map[int]int)
return sm
}
func (sm *SafeMap) readMap(key int) int {
sm.RLock()
value := sm.Map[key]
sm.RUnlock()
return value
}
func (sm *SafeMap) writeMap(key int, value int) {
sm.Lock()
sm.Map[key] = value
sm.Unlock()
}
通过sync.RWMutex读写锁控制达到安全访问map,此时会有一定的性能损耗。
建议使用==sync.Map== 是go1.9版本中提供的一种效率比较高并发安全。
- 14、go方法集 仅仅影响接口实现和方法表达式的转化,如果指针接收者实现一个接口,那么只有指向那个类型的指针才能实现对应的接口。 如果使用值接收者来实现一个接口,那么类型的值和指针都可以实现对应的接口。
a) func (u *user) notify() { show(&u) }
b1) func (u user) notify() { show(u) }
b2) func (u user) notify() { show(&u) }
- 14、panic仅有最有一个可以被recover捕获。
- 15、Array、Slice、Map、Set说明
Array:
数组是固定长度的数据类型,包含相同类型的连续元素,内存分配是连续的。初始值为0;
一个数组可以被赋值给任意相同类型(长度和元素类型)的数组。
建议在函数中不要使用数组作为参数传递。
Slice:
一种动态数组,底层内存是连续分配的。内存结构【指向底层数组的指针|长度|容量】;
var slice []int // nil slice
slice :=make([]int,0) //empty slice
slice :=[]int{} //empty slice
实用append方法可以对slice进行扩容和删除。
函数使用slice作为参数传递是比较廉价的,sizeof(slice)=16字节。
Map:
Map是一种无序的K-V集合。由于是无序的,因此无法决定迭代时返回顺序。
在函数使用map作为参数传递时,不是传递的map拷贝,而是引用类型,因此会修改原map的内容的。详情请参考:[轻松理解Go函数传参内幕](https://www.jianshu.com/p/198d9961b76a)
Set:
go本身不提供set,需要自己实现。参考:https://github.com/deckarep/golang-set
总结:array是slice和map的底层结构。slice有cap限制,但是可以通过append增加元素,map没有cap限制,可以无限增加。因此内建函数 cap 只能作用在 slice 上。在函数间传递slice和map是比较廉价的,它们不会传递底层数组的拷贝。
- 16、go 内存逃逸分析
go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就分配在栈,反之则分配在堆。
- 17、go routine 调度解析
M(machine): 代表着真正的执行计算资源,可以认为它就是os thread(系统线程)。
P(processor): 表示逻辑processor,是线程M的执行的上下文。
G(goroutine): 调度系统的最基本单位goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等。
- 18、如何定位golang性能问题
panic 调用栈
pprof
火焰图(配合压测)
使用go run -race 或者 go build -race 来进行竞争检测
查看系统 磁盘IO/网络IO/内存占用/CPU 占用(配合压测)
- 19、golang的runtime机制
Runtime 负责管理任务调度,垃圾收集及运行环境。一个重要的组成部分goroutinue scheduler,负责追踪调度每个routine运行,实际上是从应用程序的process所属的thread pool中分配一个thread来执行这个goroutine,每个gotine只有分配到一个os thread才能运行。
编译后的可执行文件,从运行角度看是有2个部分组成,1)用户代码,2)runtime。 runtime通过接口函数调用来管理goroutine,chanal等其他一些高级功能,从用户代码发起的调用操作系统的API都会被runtime拦截并处理。
网友评论