在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\n", 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是非並發安全的,使用過程中需要自己去控制加鎖。