这篇文章中,我将整理我学习Go语言过程中的笔记
string
默认字符编码方式
首先需要了解字符串内部是怎么编解码的,每个字符都对应一个独一无二的数字编号,这个映射关系叫做字符集,计算机中数据存储方式使用二进制,所以对字符串编号编码和解码的关键在于字符边界的确定方法,比较直观的方式是定长编码(即每个字符使用固定长度的二进制位—),但是这种方式比较浪费内存,Go语言默认的编码方式是utf-8,这是一种通用的变长编码方法,具体而言
字符编号范围 | 编码模板 | 例子 | 备注 |
---|---|---|---|
[0,127] | 0xxxxxxx | 字符 ‘b’ 编号是98,对应于01100010 | 使用一个字节来表示,高位0占位,剩下七位二进制位用于表示编号 |
[128,2047] | 110xxxxx 10xxxxxx | 使用两个字节来表示,高位字节,高位固定110; 低位字节,高位固定10,剩余的二进制位组合起来就是该字符的编号 |
|
[2048,65535] | 1110xxxx 10xxxxxxx 10xxxxxx | 使用三个字节来表示,组合方式和双字节类似,不作赘述 |
例如对于’字‘在UTF-8二进制编码为11100101 10101101 1011111 我们对其进行解码,将加粗的二进制组合010110110111111,编号是2782
string存储结构
Go语言中字符串变量由两部分组成:
- ptr:字符串内存起始地址
- len:字符串所占字节数量
需要注意的是,Go中的字符串分配的是只读内存,且不同的字符串变量底层可以共用相同的字符串内存空间,所以不能通过 s[1]='o'
这样来更改字符串中的字符,若要改变s字符串,可以使其重新指向一个新的字符串或者转成slice,会拷贝新的内存空间,例如
s:="hello"
ss:=([]byte)s
ss[1]='o'
slice
slice存储结构
slice会分配一段连续的内存空间,三个字段:
- data: 指向底层的数组
- len:有效数组范围,范围外为非法访问
- cap:slice指向的底层数组长度
声明slice时,data指向nil,使用make初始化slice会分配底层数组,每个元素会赋零值
不同slice对象可以共用底层数组,例如
arr :=[10]int{1,2,3,4,5,6,7,8,9,10}
slice1:= arr[1:4]
slice2:= arr[7:]
fmt.Printf("slice1 len: %v cap: %v elem %v\n",len(slice1),cap(slice1),slice1)
fmt.Printf("slice2 len: %v cap: %v elem %v\n",len(slice2),cap(slice2),slice2)
// slice1 len: 3 cap: 9 elem [2 3 4]
// slice2 len: 3 cap: 3 elem [8 9 10]
// 可以通过append 来延长slice1的长度,同时修改了底层数组
slice1 = append(slice1,-1)
fmt.Printf("slice1 len: %v cap: %v elem %v arr %v\n",len(slice1),cap(slice1),slice1,arr)
// slice1 len: 4 cap: 9 elem [2 3 4 -1] arr [1 2 3 4 -1 6 7 8 9 10]
// 若给slice2 append操作会超出cap,此时会触发拷贝并开辟新的底层数组,此时修改元素不会影响arr
ptr := unsafe.Pointer(&slice2[0])
fmt.Printf("The starting address of the slice2 before append: %p\n", ptr)
slice2 = append(slice2,-1)
slice2[0] = -1
ptr = unsafe.Pointer(&slice2[0])
fmt.Printf("The starting address of the slice2 after append: %p\n", ptr)
// append操作后len 和cap均有变化
fmt.Printf("slice2 len: %v cap: %v elem %v arr %v\n",len(slice2),cap(slice2),slice2,arr)
// slice2 len: 4 cap: 6 elem [-1 9 10 -1] arr [1 2 3 4 -1 6 7 8 9 10]
slice扩容机制
- 预估扩容后容量
slice := []int{1,2} // oldCap = 2 oldLen = 2
slice = append(slice,3,4,5) // 至少扩容到cap = 5
// 此时扩容后slice的newCap为多少?
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}