Golang数据结构|实践记录以及工具使用

45 阅读3分钟

1 array

在Golang中,array(数组)是一种固定长度的序列数据结构,其中每个元素都具有相同的类型,其长度是固定的,一旦定义后不能改变。数组的元素可以通过索引访问,索引从0开始。

2 slice

在实际编程中array有许多限制,我们很多时候需要动态数组;Golang提供了动态数组即slice数据结构,其可以动态增大或减小。

2.1 slice结构

参考Golang源码,slice被定义为一种struct类型,其成员包含指向实际数组的指针,cap和len两个int类型的变量维护slice的容量和有效长度。

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

2.2 slice的状态

在实际运行中slice可能处于三种状态:零切片、空切片、nil切片。
零切片:slice底层有指向数组但无赋值的状态。
空切片:slice底层指针指向非nil数组,但是len为0。
nil切片:slice的底层数组指向nil,即未分配空间。

2.3 slice的扩容机制

参考源码,当slice发生扩容时,对于新数组的cap选择slice分为了三种情况;当新数组的len大于两倍旧数组的cap两倍,新数组的cap为新数组的len;当旧数组的cap小于设定阈值(默认为256)时,新数组的cap为旧数组cap的两倍;前两者均不满足时,新数组的cap为旧数组cap的1.25倍+0.75阈值(默认为256)。

newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
    newcap = newLen
} else {
    const threshold = 256
    if oldCap < threshold {
       newcap = doublecap
    } else {
       // Check 0 < newcap to detect overflow
       // and prevent an infinite loop.
       for 0 < newcap && newcap < newLen {
          // 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 = newLen
       }
    }
}

3 map

Golang语言内置了map的数据结构,其底层为哈希实现,但其存在线程不安全的局限性。map底层为hmap结构,其中count变量记录map中的键值对数量;B为hmap中桶的数量的二进制长度,即通数量为2^B;overflow为溢出桶的数量;buckets和oldbuckets分别为桶指针和扩容时旧桶的指针;extra为溢出桶的地址;

type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

    extra *mapextra // optional fields
}

4 sync.Map

针对map的并发不安全缺陷,Go 语言标准库中提供的一个并发安全的映射(map)实现即sync.Map。其内部使用了锁机制来确保多个goroutine读写时的一致性,因此多goroutine访问时不会出现竞态条件;对于读多写少的场景,sync.Map使用了读写锁来提高性能,因此sync.Map适用于高并发场景。

5 栈与队列

栈与队列是两种常见的数据结构,栈有着先进后出(FILO)的特点;队列为先进先出(FIFO)的特点;很遗憾Golang官方没有提供两种数据结构的实现,Golang确实有点点简陋了,我们可以在github上找三方实现或自己手搓。