golang学习笔记
golang基础
1.unsafe包解读
绕过安全规则的方法,比如获取内存信息的sizeof方法。
unsafe包应当主要用于程序员调试,而不能出现在生产应用。
2.获得字符串的指针
sh :=(*reflect.StringHeader)(unsafe.Pointer(&s)) |
s是字符串,由于字符串的runtime底层包是私有法方法,但是reflect里面有StringHeader方法作为public的映射,因此先强转成指针,然后再强转成StringHeader。注意,这个过程中地址不会发生改变。
其中,包含一个Data指针(原始的int)和一个Len(int)。
3.rune类型是什么?
可以理解成utf8的类型。因为不管是一个汉字还是一个字母,都是一个单位的rune。因此它还和传统意义上的char有所区别。它是int32类型,也就是说是int(64)的一半。
4.切片
个人感觉go的切片相当于java中的arraylist,进行一个主动开辟空间,然后动态扩容的自动化操作。
那么关于容量调整,其实arraylist和slice都是默认扩大,而不会缩小,除非把它释放。
还有一个点就是slice可以由数组创建,因为它的底层cap是数组结构,因此可能产生slice在cap里的偏移,比如从下标为3开始,而arraylist一定是在cap(容量)里顶格的,除非做一些复杂的底层操作。
arraylist的扩容是初始为10,每次1.5倍。而slice的扩容是每次两倍(当len>1024,每次增加25%)。
这二者都线程不安全。原因在于:扩容的时候废弃原来的地址,导致其他协程(线程)会发生错误。
5.哈希
java和go的hashmap(go就是map)都采用链式寻址。一个地址一条链,叫做桶。go的桶里面包括一个bmap数组($2^B$个单位,B来自结构体的字段),有一个nextoverflow字段,指向链表中的下一个桶。一个bmap可以放bucketCnt个键值对,bucketCnt默认值为8,里面放tophash、key、elem(value)、overflow(若溢出指针)等。
关于扩容:
当负载因子 $load factor=\frac{elemNum}{bucketNum}>6.5$ 或溢出桶数量过多时会开辟新桶,届时采用渐进式(incremental)扩容,即老的桶依然存在并保存在对象的oldBucket字段,当每次操作map(插入或删除)之后会迁移一定量的桶,直到全部完成,然后对老桶数组进行垃圾回收。
也正因为渐进式扩容,因此线程也不安全,因此有必要加互斥锁(mutex)。
关于Sync.Map
由于扩容的线程不安全,衍生出官方的数据结构——Sync包下的Map。
该结构有四个字段:mu,read,dirty和missed。
主要分成两线存key,分为read和dirty两种存储结构,而value共用一份内存,适用于读多写少的情况。
查、改:一般情况读走read(一个只读快照),若read中找不到,锁住mu,走dirty。
追加:添加数据走dirty(写时才发生read的复制并追加),而在读read会有未命中次数累加到missed字段,missed达到len(dirty)的时候,将发生《dirty上移》,也就是把read指针指向dirty,dirty指针指向nil。
删除:直接让read的某个key指向nil。如果是dirty中的追加键(read中找不到的key)被删除,则上提成read时给nil改成expunged,用于标记下一次复制到新dirty时不再考虑这个key。