zoukankan      html  css  js  c++  java
  • golang学习笔记---map

    Go里的map用于存放key/value对,在其它地方常称为hash、dictionary、关联数组,这几种称呼都是对同一种数据结构的不同称呼,它们都用于将key经过hash函数处理,然后映射到value,实现一一对应的关系。

    map的内部结构

    一个简单的map结构示意图:

    在向map中存储元素的时候,会将每个key经过hash运算,根据运算得到的hash值选择合适的hash bucket(hash桶),让后将各个key/value存放到选定的hash bucket中。如果一来,整个map将根据bucket被细分成很多类别,每个key可能会交叉地存放到不同的bucket中。

    所以,map中的元素是无序的,遍历时的顺序是随机的,即使两次以完全相同的顺序存放完全相同的元素,也无法保证遍历时的顺序。

    由于要对key进行hash计算选择hash bucket,所以map的key必须具有唯一性,否则计算出的hash值相同,将人为出现hash冲撞。

    在访问、删除元素时,也类似,都要计算key的hash值,然后找到对应的hash bucket,进而找到hash bucket中的key和value。

    Go中的map是一个指针,它的底层是数组,而且用到了两个数组,其中一个更底层的数组用于打包保存key和value。

    创建、访问map

    可以通过make()创建map,它会先创建好底层数据结构,然后再创建map,并让map指向底层数据结构。

    my_map := make(map[string]int)

    其中[string]表示map的key的数据类型,int表示key对应的值。

    也可以直接通过大括号创建并初始化赋值:

    // 空map
    my_map := map[string]string{}
    
    // 初始化赋值
    my_map := map[string]string{"Red": "#da1337","Orange": '#e95a22"}
    
    // 格式化赋值
    my_map := map[string]int{
    				"Java":11,
    				"Perl":8,
    				"Python":13,      // 注意结尾的逗号不能少
    			}
    

      

    其中map的key可以是任意内置的数据类型(如int),或者其它可以通过"=="进行等值比较的数据类型,如interface和指针可以。slice、数组、map、struct类型都不能作为key。

    但value基本可以是任意类型,例如嵌套一个slice到map中:

    my_map := map[string][]int{}
    

      

    访问map中的元素时,指定它的key即可,注意string类型的key必须加上引号:

    my_map := map[string]int{
    				"Java":11,
    				"Perl":8,
    				"Python":13,
    			}
    
    // 访问
    println(my_map["Perl"])
    
    // 赋值已有的key & value
    my_map["Perl"] = 12
    println(my_map["Perl"])
    
    // 赋值新的key & value
    my_map["Shell"] = 14
    println(my_map["Shell"])

    nil map和空map

    空map是不做任何赋值的map:

    // 空map
    my_map := map[string]string{} 

    nil map,它将不会做任何初始化,不会指向任何数据结构:

    // nil map
    var my_map map[string]string

    nil map和empty map的关系,就像nil slice和empty slice一样,两者都是空对象,未存储任何数据,但前者不指向底层数据结构,后者指向底层数据结构,只不过指向的底层对象是空对象。使用println输出看下即可知道:

    package main
    
    func main() {
    	var nil_map map[string]string
    	println(nil_map)
    
    	emp_map := map[string]string{}
    	println(emp_map)
    }
    

      

    输出结果:

    0x0
    0xc04204de38
    

    所以,map类型实际上就是一个指针

    map中元素的返回值

    当访问map中某个元素的时候,有两种返回值的格式:

    value := my_map["key"]
    value,exists := my_map["key"]
    

      

    第一种很好理解,就是检索map中key对应的value值。如果key不存在,则value返回值对应数据类型的0。例如int为数值0,布尔为false,字符串为空""。

    第二种不仅返回key对应的值,还根据key是否存在返回一个布尔值赋值给exists变量。所以,当key存在时,value为对应的值,exists为true;当key不存在,value为0(同样是各数据类型所代表的0),exists为false。

    看下例子:

    package main
    
    //	"fmt"
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    	}
    
    	value1 := my_map["Python"]
    	value2, exists2 := my_map["Perl"]
    	value3, exists3 := my_map["Shell"]
    
    	println(value1)
    	println(value2, exists2)
    	println(value3, exists3)
    }
    

      

    上面将输出如下结果:

    13
    8 true
    0 false
    

    在Go中设置类似于这种多个返回值的情况很多,即便是自己编写函数也会经常设置它的exists属性。

    len()和delete()

    len()函数用于获取map中元素的个数,即有多个少key。delete()用于删除map中的某个key。

    package main
    
    //	"fmt"
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    		"Shell":  23,
    	}
    
    	println(len(my_map)) // 4
    
    	delete(my_map, "Perl")
    
    	println(len(my_map)) // 3
    }
    

      

    测试map中元素是否存在

    两种方式可以测试map中是否存在某个key:

    1. 根据map元素的第二个返回值来判断
    2. 根据返回的value是否为0(不同数据类型的0不同)来判断

    方式一:直接访问map中的该元素,将其赋值给两个变量,第二个变量就是元素是否存在的修饰变量。

    package main
    
    //	"fmt"
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    	}
    
    	value, exists := my_map["Perl"]
    
    	if exists {
    		println("The key exists in map")
    		println(value)
    	}
    
    }

    可以将上面两个步骤合并起来,看着更高大上一些:

    if value,exists := my_map["Perl"];exists {
        println("key exists in map")
    }
    

     

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   0,
    		"Python": 13,
    	}
    
    	_, exist := my_map["Perl"]
    	if exist {
    		fmt.Println("exists in map")
    	}
    
    }
    

      

    方式二:根据map元素返回的value判断。因为该map中的value部分是int类型,所以它的0是数值的0。

    my_map := map[string]int{
    				"Java":11,
    				"Perl":8,
    				"Python":13,
    			}
    
    value := my_map["Shell"]
    if value == 0 {
        println{"not exists in map"}
    }
    

      

    如果map的value数据类型是string,则判断是否为空:

    if value == "" {
        println("not exists in map")
    }
    

      

    由于map中的value有可能本身是存在的,但它的值为0,这时就会出现误判断。例如下面的"Shell",它已经存在,但它对应的值为0。

    my_map := map[string]int{
    				"Java":11,
    				"Perl":8,
    				"Python":13,
    				"Shell":0,
    			}

    所以,应当使用第一种方式进行判断元素是否存在。

    迭代遍历map

    因为map是key/value类型的数据结构,key就是map的index,所以range关键字对map操作时,将返回key和value。

    package main
    
    //"fmt"
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    		"Shell":  23,
    	}
    
    	for key, value := range my_map {
    		println("key:", key, " value:", value)
    	}
    
    }
    

      

    如果range迭代map时,只给一个返回值,则表示迭代map的key:

    package main
    
    //"fmt"
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    		"Shell":  23,
    	}
    
    	for key := range my_map {
    		println("key:", key)
    	}
    
    	for _, value := range my_map {
    		println("value:", value)
    	}
    
    }
    

      

    获取map中所有的key

    Go中没有提供直接获取map所有key的函数。所以,只能自己写,方式很简单,range遍历map,将遍历到的key放进一个slice中保存起来。

    package main
    
    import "fmt"
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    		"Shell":  23,
    	}
    
    	// 保存map中key的slice
    	// slice类型要和map的key类型一致
    	keys := make([]string, 0, len(my_map))
    
    	// 将map中的key遍历到keys中
    	for map_key, _ := range my_map {
    		keys = append(keys, map_key)
    	}
    
    	fmt.Println(keys)
    }
    

      

    注意上面声明的slice中要限制长度为0,否则声明为长度4、容量4的slice,而这4个元素都是空值,而且后面append()会直接对slice进行一次扩容,导致append()后的slice长度为map长度的2倍,前一半为空,后一般才是map中的key。

    传递map给函数

    map是一种指针,所以将map传递给函数,仅仅只是复制这个指针,所以函数内部对map的操作会直接修改外部的map。

    例如,addone()用于给map的key对应的值加1。

    package main
    
    func main() {
    	my_map := map[string]int{
    		"Java":   11,
    		"Perl":   8,
    		"Python": 13,
    		"Shell":  23,
    	}
    
    	println(my_map["Perl"])   // 8
    	addone(my_map,"Perl") 
    	println(my_map["Perl"])   // 9
    }
    
    func addone(m map[string]int,key string) {
    	m[key] += 1
    }
    

      

    使用函数作为map的值

    map的值可以是任意对象,包括函数、指针、stuct等等。如果将函数作为key映射的值,则可以用于实现一种分支结构。

    map_func := map[KEY_TYPE]func() RETURN_TYPE {......}
    map_func := make(map[KEY_TYPE]func() RETURN_TYPE)
    

      

    例如:

    package main
    
    import "fmt"
    
    func main() {
    	mf := map[int]func() int{
    		1: func() int { return 10 },
    		2: func() int { return 20 },
    		5: func() int { return 50 },
    	}
    	fmt.Println(mf) // 输出函数的指针
    	a := mf[1]()    // 调用某个分支的函数
    	println(a)
    }
    
    package main
    
    import "fmt"
    
    func main() {
    	mf := make(map[int]func() string)
    	mf[1] = func() string { return "10" }
    	mf[2] = func() string { return "20" }
    	mf[3] = func() string { return "30" }
    	mf[4] = func() string { return "40" }
    
    	fmt.Println(mf[2]())
    }
    

      

  • 相关阅读:
    WPF 使用 Direct2D1 画图 绘制基本图形
    WPF 使用 Direct2D1 画图 绘制基本图形
    dot net core 使用 IPC 进程通信
    dot net core 使用 IPC 进程通信
    win2d 图片水印
    win2d 图片水印
    Java实现 LeetCode 240 搜索二维矩阵 II(二)
    PHP closedir() 函数
    PHP chroot() 函数
    PHP chdir() 函数
  • 原文地址:https://www.cnblogs.com/saryli/p/13273125.html
Copyright © 2011-2022 走看看