map的容量的获取

在go语言中,有两个内建函数分别是len(),cap(),前者用于获取容器的具体内容个数,后者用于获取容器分配的容量大小,但是这个cap对象是不能获取到map具体分配的容量大小的。有没有办法获取到呢,办法是有的,且看下文。

首先我们先使用gdb调试工具,查看一下map对象的具体结构是什么样子的。

一个及其简单的代码如下:

package main
func main() {
   m := make(map[string]int)
   m["a"] = 1
   m["b"] = 2
   
}

接下来我们编译这个简单的代码,并进行调试

# go build -o test -gcflags="-N -l" main.go 这里goflags是编译时指定的参数, -N 表示禁止优化,-l表示禁止内联。 便于调试

使用gdb进行调试

# gdb test
(gdb) b main.main
Breakpoint 1 at 0x401000: file /media/sf_goproject/src/map.go, line 3.
(gdb) r # 开始运行
Starting program: /media/sf_goproject/src/test
(gdb) n
m := make(map[string]int)
(gdb) ptype m

type = struct hash<string, int> {

int count;

uint8 flags;

uint8 B;

uint32 hash0;

struct bucket<string, int> *buckets;

struct bucket<string, int> *oldbuckets;

uintptr nevacuate;

[2]*[]*runtime.bmap *overflow;

} *

从上面的调试可以看到map对象的数据结构,在golang的runtime包的的haspmap.go中有这个结构的详细介绍:

// A header for a Go map.
type hmap struct {
	// Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and
	// ../reflect/type.go. Don't change this structure without also changing that code!
	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)
	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)

	// If both key and value do not contain pointers and are inline, then we mark bucket
	// type as containing no pointers. This avoids scanning such maps.
	// However, bmap.overflow is a pointer. In order to keep overflow buckets
	// alive, we store pointers to all overflow buckets in hmap.overflow.
	// Overflow is used only if key and value do not contain pointers.
	// overflow[0] contains overflow buckets for hmap.buckets.
	// overflow[1] contains overflow buckets for hmap.oldbuckets.
	// The first indirection allows us to reduce static size of hmap.
	// The second indirection allows to store a pointer to the slice in hiter.
	overflow *[2]*[]*bmap
}



所以B代表了map的容量 既然我们知道了数据的的结构,便可根据结构得到容量的内容。代码示例如下。

package main

type hmap struct {
    count int 
    flags uint8
    B     uint8  
    hash0 uint32
    buckets    unsafe.Pointer 
    oldbuckets unsafe.Pointer 
}


func main() {
    m := make(map[string]string)
    c, b := getInfo(m)
    fmt.Println("count: ", c, "b: ", b)
    for i := 0; i < 10000; i++ {
        m[strconv.Itoa(i)] = strconv.Itoa(i)
        if i%200 == 0 {
            c, b := getInfo(m)
            cap := math.Pow(float64(2), float64(b))
            fmt.Printf("count: %d, b: %d, load: %f
", c, b, float64(c)/cap)
        }
    }
    println("开始删除------")
    for i := 0; i < 10000; i++ {
        delete(m, strconv.Itoa(i))
        if i%200 == 0 {
            c, b := getInfo(m)
            cap := math.Pow(float64(2), float64(b))
            fmt.Println("count: ", c, "b:", b, "load: ", float64(c)/cap)
        }
    }


    debug.FreeOSMemory()
    c, b = getInfo(m)
    fmt.Println("释放后: ", "count: ", c, "b:", b)
}


func getInfo(m map[string]string) (int, int) {
    point := (**hmap)(unsafe.Pointer(&m))
    value := *point
    return value.count, int(value.B)
}

一些记录:
1. 在看许多文章中有说到map分配的键值被删除之后,内存是不会释放的。但是在我测试的过程中,发现内存是可以释放的。可能是版本的原因,测试的版本是1.7.1
2. map是非并发安全的,使用过程中需要自己去控制加锁。

原文地址:https://www.cnblogs.com/qiumingcheng/p/9948472.html