Go学习笔记-map

Go 语言的字典类型其实是一个哈希表(hash table)的特定实现。
在这个实现中,键和元素的最大不同在于,前者的类型是受限的,不可以是函数类型、字典类型和切片类型(因为不支持 == 和 != 判等操作 ),而后者却可以是任意类型的。

键和元素的这种对应关系在数学里就被称为“映射”,这也是“map”这个词的本意,哈希表的映射过程就存在于对键 - 元素对的增、删、改、查的操作之中

aMap := map[string]int{
    "one":    1,
    "two":    2,
    "three": 3,
}
k := "two"
v, ok := aMap[k]
if ok {
    fmt.Printf("The element of key %q: %d\n", k, v)
} else {
    fmt.Println("Not found!")
}

映射过程的第一步就是把键值转换为哈希值。在 Go 语言的字典中,每一个键值都是由它的哈希值代表的。

宽度越小的类型速度通常越快。对于字符串类型,由于它的宽度是不定的,所以要看它的值的具体长度,长度越短求哈希越快。

由于字典是引用类型,所以当我们仅声明而不初始化一个字典类型的变量的时候,它的值会是nil。

除了添加键 - 元素对,我们在一个值为nil的字典上做任何操都不会引起错误。当我们试图在一个值为nil的字典中添加键 - 元素对的时候,Go 语言的运行时系统就会立即抛出一个panic.

package main

import "fmt"

func main() {
	var m map[string]int

	key := "two"
	elem, ok := m["two"]
	fmt.Printf("The element paired with key %q in nil map: %d (%v)\n",
		key, elem, ok)

	fmt.Printf("The length of nil map: %d\n",
		len(m))

	fmt.Printf("Delete the key-element pair by key %q...\n",
		key)
	delete(m, key)

	elem = 2
	fmt.Println("Add a key-element pair to a nil map...")
	m["two"] = elem // 这里会引发panic。
}

map基本数据结构
map的底层结构是hmap(即hashmap的缩写),核心元素是一个由若干个桶(bucket,结构为bmap)组成的数组,每个bucket可以存放若干元素(通常是8个),key通过哈希算法被归入不同的bucket中。当超过8个元素需要存入某个bucket时,hmap会使用extra中的overflow来拓展该bucket。下面是hmap的结构体。

type hmap struct {
    count     int // # 元素个数
    flags     uint8
    B         uint8  // 说明包含2^B个bucket
    noverflow uint16 // 溢出的bucket的个数
    hash0     uint32 // hash种子
 
    buckets    unsafe.Pointer // buckets的数组指针
    oldbuckets unsafe.Pointer // 结构扩容的时候用于复制的buckets数组
    nevacuate  uintptr        // 搬迁进度(已经搬迁的buckets数量)
 
    extra *mapextra
}

extra中不仅有overflow,还有oldoverflow(用于扩容)和nextoverflow(prealloc的地址)。

bucket(bmap)的结构如下

type bmap struct {
    // tophash generally contains the top byte of the hash value
    // for each key in this bucket. If tophash[0] < minTopHash,
    // tophash[0] is a bucket evacuation state instead.
    tophash [bucketCnt]uint8
    // Followed by bucketCnt keys and then bucketCnt values.
    // NOTE: packing all the keys together and then all the values together makes the
    // code a bit more complicated than alternating key/value/key/value/... but it allows
    // us to eliminate padding which would be needed for, e.g., map[int64]int8.
    // Followed by an overflow pointer.
}


tophash用于记录8个key哈希值的高8位,这样在寻找对应key的时候可以更快,不必每次都对key做全等判断。
注意后面几行注释,hmap并非只有一个tophash,而是后面紧跟8组kv对和一个overflow的指针,这样才能使overflow成为一个链表的结构。但是这两个结构体并不是显示定义的,而是直接通过指针运算进行访问的。
kv的存储形式为”key0key1key2key3…key7val1val2val3…val7″,这样做的好处是:在key和value的长度不同的时候,节省padding空间。如上面的例子,在map[int64]int8中,4个相邻的int8可以存储在同一个内存单元中。如果使用kv交错存储的话,每个int8都会被padding占用单独的内存单元(为了提高寻址速度)。

map练习

package main

import (
	"fmt"
	"sort"
)

func main() {
	//定义一个map并初始化,根据key获取value
	colors := map[string]string{
		"bird":  "blue",
		"snake": "green",
		"cat":   "black",
	}
	// Get color of snake.
	c := colors["snake"]

	// Display string.
	fmt.Println(c)

	//通过==进行map赋值
	colors["rabbit"] = "white"
	fmt.Println(colors)

	names := map[int]string{}
	// Add three pairs to the map in separate statements.
	names[990] = "file.txt"
	names[1009] = "data.xls"
	names[1209] = "image.jpg"

	// There are three pairs in the map.
	fmt.Println(len(names))

	//通过delete删除
	stus := map[int]string{}
	stus[0] = "zhangsan"
	stus[1] = "lisi"
	stus[2] = "wang5"
	stus[3] = "ma6"
	fmt.Println(stus)

	delete(stus, 2)
	fmt.Println(stus)

	//map的range遍历
	animals := map[string]string{}
	animals["cat"] = "Mitty"
	animals["dog"] = "hashi"
	//look over the map
	for key, value := range animals {
		fmt.Println(key, "=", value)
	}

	//获取map中所有的key
	sizes := map[string]int{
		"XL": 20,
		"L":  10,
		"M":  5,
	}

	// Loop over map and append keys to empty slice.
	keys := []string{}
	for key, _ := range sizes {
		keys = append(keys, key)
	}

	// This is a slice of the keys.
	fmt.Println(keys)

	//通过make进行构建
	lookup := make(map[string]int, 200)

	// Use the new map.
	lookup["cat"] = 10
	result := lookup["cat"]
	fmt.Println(result)

	//map作为函数参数
	// This map has two string keys.
	colors2 := map[string]int{
		"blue":  10,
		"green": 20,
	}
	// Pass map to func.
	PrintGreen(colors2)

	//按key进行排序
	// To create a map as input
	m := make(map[int]string)
	m[1] = "a"
	m[2] = "c"
	m[0] = "b"

	// To store the keys in slice in sorted order
	var keys2 []int
	for k := range m {
		fmt.Println(k) //每次都随机输出key的值
		keys2 = append(keys2, k)
	}
	sort.Ints(keys2)

	// To perform the opertion you want
	for _, k := range keys2 {
		fmt.Println("Key:", k, "Value:", m[k])
	}


}

func PrintGreen(colors map[string]int) {
	// Handle map argument.
	fmt.Println(colors["green"])
}

使用中常见问题
Q:删除掉map中的元素是否会释放内存?
A:不会,删除操作仅仅将对应的tophash[i]设置为empty,并非释放内存。若要释放内存只能等待指针无引用后被系统gc

Q:如何并发地使用map?
A:map不是goroutine安全的,所以在有多个gorountine对map进行写操作是会panic。多gorountine读写map是应加锁(RWMutex),或使用sync.Map(1.9新增,在下篇文章中会介绍这个东西,总之是不太推荐使用)。

Q:map的iterator是否安全?
A:map的delete并非真的delete,所以对迭代器是没有影响的,是安全的。

非原子操作需要加锁, map并发读写需要加锁,map操作不是并发安全的,判断一个操作是否是原子的可以使用 go run race 命令做数据的竞争检测.
 

猜你喜欢

转载自blog.csdn.net/Linzhongyilisha/article/details/83180805
今日推荐